Coder Social home page Coder Social logo

fastapi-tips's Introduction

101 FastAPI Tips by The FastAPI Expert

This repository contains trips and tricks for FastAPI. If you have any tip that you believe is useful, feel free to open an issue or a pull request.

Consider sponsor me on GitHub to support my work. With your support, I will be able to create more content like this.

GitHub Sponsors

Tip

Remember to watch this repository to receive notifications about new tips.

1. Install uvloop and httptools

By default, Uvicorn doesn't comes with uvloop and httptools which are faster than the default asyncio event loop and HTTP parser. You can install them using the following command:

pip install uvloop httptools

Uvicorn will automatically use them if they are installed in your environment.

Warning

uvloop can't be installed on Windows. If you use Windows locally, but Linux on production, you can use an environment marker to not install uvloop on Windows e.g. uvloop; sys_platform != 'win32'.

2. Be careful with non-async functions

There's a performance penalty when you use non-async functions in FastAPI. So, always prefer to use async functions. The penalty comes from the fact that FastAPI will call run_in_threadpool, which will run the function using a thread pool.

Note

Internally, run_in_threadpool will use anyio.to_thread.run_sync to run the function in a thread pool.

Tip

There are only 40 threads available in the thread pool. If you use all of them, your application will be blocked.

To change the number of threads available, you can use the following code:

import anyio
from contextlib import asynccontextmanager
from typing import Iterator

from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI) -> Iterator[None]:
    limiter = anyio.to_thread.current_default_thread_limiter()
    limiter.total_tokens = 100
    yield

app = FastAPI(lifespan=lifespan)

You can read more about it on AnyIO's documentation.

3. Use async for instead of while True on WebSocket

Most of the examples you will find on the internet use while True to read messages from the WebSocket.

I believe the uglier notation is used mainly because the Starlette documentation didn't show the async for notation for a long time.

Instead of using the while True:

from fastapi import FastAPI
from starlette.websockets import WebSocket

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Message text was: {data}")

You can use the async for notation:

from fastapi import FastAPI
from starlette.websockets import WebSocket

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
    await websocket.accept()
    async for data in websocket.iter_text():
        await websocket.send_text(f"Message text was: {data}")

You can read more about it on the Starlette documentation.

4. Ignore the WebSocketDisconnect exception

If you are using the while True notation, you will need to catch the WebSocketDisconnect. The async for notation will catch it for you.

from fastapi import FastAPI
from starlette.websockets import WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket) -> None:
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Message text was: {data}")
    except WebSocketDisconnect:
        pass

If you need to release resources when the WebSocket is disconnected, you can use that exception to do it.

If you are using an older FastAPI version, only the receive methods will raise the WebSocketDisconnect exception. The send methods will not raise it. In the latest versions, all methods will raise it. In that case, you'll need to add the send methods inside the try block.

5. Use HTTPX's AsyncClient instead of TestClient

Since you are using async functions in your application, it will be easier to use HTTPX's AsyncClient instead of Starlette's TestClient.

from fastapi import FastAPI


app = FastAPI()


@app.get("/")
async def read_root():
    return {"Hello": "World"}


# Using TestClient
from starlette.testclient import TestClient

client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"Hello": "World"}

# Using AsyncClient
import anyio
from httpx import AsyncClient, ASGITransport


async def main():
    async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client:
        response = await client.get("/")
        assert response.status_code == 200
        assert response.json() == {"Hello": "World"}


anyio.run(main)

If you are using lifespan events (on_startup, on_shutdown or the lifespan parameter), you can use the asgi-lifespan package to run those events.

from contextlib import asynccontextmanager
from typing import AsyncIterator

import anyio
from asgi_lifespan import LifespanManager
from httpx import AsyncClient, ASGITransport
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    print("Starting app")
    yield
    print("Stopping app")


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def read_root():
    return {"Hello": "World"}


async def main():
    async with LifespanManager(app) as manager:
        async with AsyncClient(transport=ASGITransport(app=manager.app)) as client:
            response = await client.get("/")
            assert response.status_code == 200
            assert response.json() == {"Hello": "World"}


anyio.run(main)

Note

Consider supporting the creator of asgi-lifespan Florimond Manca via GitHub Sponsors.

6. Use Lifespan State instead of app.state

Since not long ago, FastAPI supports the lifespan state, which defines a standard way to manage objects that need to be created at startup, and need to be used in the request-response cycle.

The app.state is not recommended to be used anymore. You should use the lifespan state instead.

Using the app.state, you'd do something like this:

from contextlib import asynccontextmanager
from typing import AsyncIterator

from fastapi import FastAPI, Request
from httpx import AsyncClient


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    async with AsyncClient(app=app) as client:
        app.state.client = client
        yield


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def read_root(request: Request):
    client = request.app.state.client
    response = await client.get("/")
    return response.json()

Using the lifespan state, you'd do something like this:

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import Any, TypedDict, cast

from fastapi import FastAPI, Request
from httpx import AsyncClient


class State(TypedDict):
    client: AsyncClient


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[State]:
    async with AsyncClient(app=app) as client:
        yield {"client": client}


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def read_root(request: Request) -> dict[str, Any]:
    client = cast(AsyncClient, request.state.client)
    response = await client.get("/")
    return response.json()

7. Enable AsyncIO debug mode

If you want to find the endpoints that are blocking the event loop, you can enable the AsyncIO debug mode.

When you enable it, Python will print a warning message when a task takes more than 100ms to execute.

Run the following code with PYTHONASYNCIODEBUG=1 python main.py:

import os
import time

import uvicorn
from fastapi import FastAPI


app = FastAPI()


@app.get("/")
async def read_root():
    time.sleep(1)  # Blocking call
    return {"Hello": "World"}


if __name__ == "__main__":
    uvicorn.run(app, loop="uvloop")

If you call the endpoint, you will see the following message:

INFO:     Started server process [19319]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:50036 - "GET / HTTP/1.1" 200 OK
Executing <Task finished name='Task-3' coro=<RequestResponseCycle.run_asgi() done, defined at /uvicorn/uvicorn/protocols/http/httptools_impl.py:408> result=None created at /uvicorn/uvicorn/protocols/http/httptools_impl.py:291> took 1.009 seconds

You can read more about it on the official documentation.

8. Implement a Pure ASGI Middleware instead of BaseHTTPMiddleware

The BaseHTTPMiddleware is the simplest way to create a middleware in FastAPI.

Note

The @app.middleware("http") decorator is a wrapper around the BaseHTTPMiddleware.

There were some issues with the BaseHTTPMiddleware, but most of the issues were fixed in the latest versions. That said, there's still a performance penalty when using it.

To avoid the performance penalty, you can implement a Pure ASGI middleware. The downside is that it's more complex to implement.

Check the Starlette's documentation to learn how to implement a Pure ASGI middleware.

9. Your dependencies may be running on threads

If the function is non-async and you use it as a dependency, it will run in a thread.

In the following example, the http_client function will run in a thread:

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

from httpx import AsyncClient
from fastapi import FastAPI, Request, Depends


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[dict[str, AsyncClient]]:
    async with AsyncClient() as client:
        yield {"client": client}


app = FastAPI(lifespan=lifespan)


def http_client(request: Request) -> AsyncClient:
    return request.state.client


@app.get("/")
async def read_root(client: AsyncClient = Depends(http_client)):
    return await client.get("/")

To run in the event loop, you need to make the function async:

# ...

async def http_client(request: Request) -> AsyncClient:
    return request.state.client

# ...

As an exercise for the reader, let's learn a bit more about how to check the running threads.

You can run the following with python main.py:

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

import anyio
from anyio.to_thread import current_default_thread_limiter
from httpx import AsyncClient
from fastapi import FastAPI, Request, Depends


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[dict[str, AsyncClient]]:
    async with AsyncClient() as client:
        yield {"client": client}


app = FastAPI(lifespan=lifespan)


# Change this function to be async, and rerun this application.
def http_client(request: Request) -> AsyncClient:
    return request.state.client


@app.get("/")
async def read_root(client: AsyncClient = Depends(http_client)): ...


async def monitor_thread_limiter():
    limiter = current_default_thread_limiter()
    threads_in_use = limiter.borrowed_tokens
    while True:
        if threads_in_use != limiter.borrowed_tokens:
            print(f"Threads in use: {limiter.borrowed_tokens}")
            threads_in_use = limiter.borrowed_tokens
        await anyio.sleep(0)


if __name__ == "__main__":
    import uvicorn

    config = uvicorn.Config(app="main:app")
    server = uvicorn.Server(config)

    async def main():
        async with anyio.create_task_group() as tg:
            tg.start_soon(monitor_thread_limiter)
            await server.serve()

    anyio.run(main)

If you call the endpoint, you will see the following message:

โฏ python main.py
INFO:     Started server process [23966]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Threads in use: 1
INFO:     127.0.0.1:57848 - "GET / HTTP/1.1" 200 OK
Threads in use: 0

Replace the def http_client with async def http_client and rerun the application. You will not see the message Threads in use: 1, because the function is running in the event loop.

Tip

You can use the FastAPI Dependency package that I've built to make it explicit when a dependency should run in a thread.

fastapi-tips's People

Contributors

hadrien avatar kdcokenny avatar kludex avatar sadikkuzu avatar the-code-rider avatar theobabilon avatar vvanglro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastapi-tips's Issues

how to disable uvicorn / fastapi default logging

thanks for the initiative @Kludex

one question that i struggle to find a solid answer + i saw many horrors in the wild trying to do it:

how to disable the default logs from uvicorn / fastapi

and now with the fastapi cli fastapi dev .. / fastapi run ..

how to achieve the "unix rule of silence", if is running correctly, no logs are displayed?

CleanShot 2024-07-23 at 01 11 16

Lifespan question

Hey, First of all thanks for this initiative, it's great!
I have a question regarding the lifespan to manage objects.
Let's say that I would like to provide realtime data, and I have an object that subscribes via ws and keeps certain data updated, so I can expose a route to provide this data aggregated. Should I create the object with the lifespan approach? Or which is your recommendation?

#5 incorrect lifespan pass-in

async with LifespanManager(app, lifespan) as manager: is incorrect and throws the error: TypeError: '<=' not supported between instances of 'function' and 'int'.

Based on this, the example should be:

from contextlib import asynccontextmanager
from typing import AsyncIterator

import anyio
from asgi_lifespan import LifespanManager
from httpx import AsyncClient, ASGITransport
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    print("Starting app")
    yield
    print("Stopping app")


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def read_root():
    return {"Hello": "World"}


async def main():
    async with LifespanManager(app) as manager:
        async with AsyncClient(transport=ASGITransport(app=manager.app)) as client:
            response = await client.get("/")
            assert response.status_code == 200
            assert response.json() == {"Hello": "World"}


anyio.run(main)

Instead of the current:

from contextlib import asynccontextmanager
from typing import AsyncIterator

import anyio
from asgi_lifespan import LifespanManager
from httpx import AsyncClient, ASGITransport
from fastapi import FastAPI


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    print("Starting app")
    yield
    print("Stopping app")


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def read_root():
    return {"Hello": "World"}


async def main():
    async with LifespanManager(app, lifespan) as manager:
        async with AsyncClient(transport=ASGITransport(app=manager.app)) as client:
            response = await client.get("/")
            assert response.status_code == 200
            assert response.json() == {"Hello": "World"}


anyio.run(main)

Hiding docs tip

This is a question I see people asking a lot, so might be useful.

Removing fastapi docs completely:

import FastAPI

application = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)

Injecting a dependency (like get_current_superuser so only superusers are allowed to access the docs)

import FastAPI
from fastapi import Depends
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.openapi.utils import get_openapi

from .dependencies import get_current_superuser

# let's just remove the standard docs urls
application = FastAPI(
    docs_url=None, 
    redoc_url=None, 
    openapi_url=None,
    title="My API",
    version="0.1.0",
)

# and create the router with the dependency we want
docs_router = APIRouter(dependencies=[Depends(get_current_superuser)])

# and rewrite the endpoints
@docs_router.get("/docs", include_in_schema=False)
async def get_swagger_documentation() -> fastapi.responses.HTMLResponse:
    return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")

@docs_router.get("/redoc", include_in_schema=False)
async def get_redoc_documentation() -> fastapi.responses.HTMLResponse:
    return get_redoc_html(openapi_url="/openapi.json", title="docs")

@docs_router.get("/openapi.json", include_in_schema=False)
async def openapi() -> dict[str, Any]:
    out: dict = get_openapi(title=application.title, version=application.version, routes=application.routes)
    return out

# finally, let's include these in the original FastAPI application
application.include_router(docs_router)

Threadpool tokens tip

Adding a tip on setting the threadpool number of tokens might be useful

import anyio

# 100 tokens example
limiter = anyio.to_thread.current_default_thread_limiter()
limiter.total_tokens = 100

If the task is cpu bound, having the same number of tokens as cpu cores is probably the best way to go (to avoid unnecessary context switching), if the task is IO bound, using more tokens is useful. I don't know the sweet spot for optimal performance, but what I usually do is 50-100 tokens for 1-2 cores, 100-200 for 4-8 cores, 200-300 for 8+. Would be useful to actually test this.

(I believe 40 is the standard number, but you know this better than I do)

Any tips about sqlmodel

I use fastapi in my recent project but I spent a lot of time on structure my model code.

The biggest problem is split models.py to a module. I read a lot of articles and found it is not a acceptable because of pedantic.
User have to re-import all referenced model at the end of each file and rebuild models in the file. Is there a more graceful solution?

Another problem is the inherit between sqlmodels with table=True and table=False. I found table=True sqlmodel will be a sqalchemy model created by meta class and table=False is actually pedantic model. If user create a pedantic model based on sqlalchemy model, some unexpected bug may occur.

Can you write some tips about fastapi models?

AsyncClient app parameter deprecation

Hi @Kludex ,

Not sure if relevant, but whilst following tip 5 Use HTTPX's AsyncClient instead of TestClient in one of my current FastAPIs, I was only receiving 302s status codes:

async def test_client():
    async with AsyncClient(app=dividend_app, base_url="http://testserver") as client:
        resp = await client.get("/user/me")

getting

(Pdb++) resp
<Response [302 Found]>
(Pdb++) resp.text
'<HTML>\r\n<HEAD><TITLE>Redirection</TITLE></HEAD>\r\n<BODY><H1>Redirect</H1></BODY>\r\n</HTML>\r\n'

I can reproduce from current README's MCVE.
Steps to reproduce:

uv venv
source .venv/bin/activate
uv pip install fastapi[all]

Then running mcve.py:

from fastapi import FastAPI


app = FastAPI()


@app.get("/")
async def read_root():
    return {"Hello": "World"}


# Using TestClient
from starlette.testclient import TestClient

client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"Hello": "World"}

# Using AsyncClient
import anyio
from httpx import AsyncClient


async def main():
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.get("/")
        assert response.status_code == 200
        assert response.json() == {"Hello": "World"}


anyio.run(main)

gives

  File "/path/to/mcve.py", line 29, in main
    assert response.status_code == 200
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

status_code is indeed 302.

I believe it comes from httpx's AsyncClient app parameter deprecation in 0.27.0.

Changing to

async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as client

fixes the issue, making the above MCVE pass.

FWIW, uv pip list gives

$> uv pip list
Package              Version
-------------------- --------
annotated-types      0.6.0
anyio                4.3.0
certifi              2024.2.2
click                8.1.7
dnspython            2.6.1
email-validator      2.1.1
fastapi              0.110.3
h11                  0.14.0
httpcore             1.0.5
httptools            0.6.1
httpx                0.27.0
idna                 3.7
itsdangerous         2.2.0
jinja2               3.1.3
markupsafe           2.1.5
orjson               3.10.2
pydantic             2.7.1
pydantic-core        2.18.2
pydantic-extra-types 2.7.0
pydantic-settings    2.2.1
python-dotenv        1.0.1
python-multipart     0.0.9
pyyaml               6.0.1
sniffio              1.3.1
starlette            0.37.2
typing-extensions    4.11.0
ujson                5.9.0
uvicorn              0.29.0
uvloop               0.19.0
watchfiles           0.21.0
websockets           12.0

uvloop is not supported on windows

issue

i tried installing uvloop on my machine windows 11; python 3.10.0. i have added the error message in the screenshot.
it was bit of a bummer, considering that this is the first tip and is not supported in windows. while i can probably install it while building the docker image, i don't want to keep docker and local setup different.

image

suggested solution

should we perhaps add a warning in the doc, or will it look ugly/unnecessary?

First tip not working

I'm trying to apply the first tip with the following packages in the requirements.txt file:

fastapi[all]==0.111.0
httptools==0.6.1
uvloop==0.4.12

But I get this error:

ERROR: Cannot install httptools==0.6.1, uvicorn[standard]==0.13.3, uvicorn[standard]==0.13.4, uvicorn[standard]==0.15.0, uvicorn[standard]==0.16.0, uvicorn[standard]==0.17.0.post1, uvicorn[standard]==0.17.1, uvicorn[standard]==0.17.2, uvicorn[standard]==0.17.3, uvicorn[standard]==0.17.4, uvicorn[standard]==0.17.5, uvicorn[standard]==0.17.6, uvicorn[standard]==0.18.0, uvicorn[standard]==0.18.1, uvicorn[standard]==0.18.2, uvicorn[standard]==0.18.3, uvicorn[standard]==0.19.0, uvicorn[standard]==0.20.0, uvicorn[standard]==0.21.0, uvicorn[standard]==0.21.1, uvicorn[standard]==0.22.0, uvicorn[standard]==0.23.0, uvicorn[standard]==0.23.1, uvicorn[standard]==0.23.2, uvicorn[standard]==0.24.0, uvicorn[standard]==0.24.0.post1, uvicorn[standard]==0.25.0, uvicorn[standard]==0.26.0, uvicorn[standard]==0.27.0, uvicorn[standard]==0.27.0.post1, uvicorn[standard]==0.27.1, uvicorn[standard]==0.28.0, uvicorn[standard]==0.28.1, uvicorn[standard]==0.29.0, uvicorn[standard]==0.30.0, uvicorn[standard]==0.30.1 and uvloop==0.4.12 because these package versions have conflicting dependencies.
The conflict is caused by:
The user requested uvloop==0.4.12
uvicorn[standard] 0.30.1 depends on uvloop!=0.15.0, !=0.15.1 and >=0.14.0; (sys_platform != "win32" and (sys_platform != "cygwin" and platform_python_implementation != "PyPy")) and extra == "standard"
The user requested uvloop==0.4.12
uvicorn[standard] 0.30.0 depends on uvloop!=0.15.0, !=0.15.1 and >=0.14.0; (sys_platform != "win32" and (sys_platform != "cygwin" and platform_python_implementation != "PyPy")) and extra == "standard"
[...]
The user requested httptools==0.6.1
uvicorn[standard] 0.17.5 depends on httptools<0.4.0 and >=0.2.0; extra == "standard"
The user requested httptools==0.6.1
uvicorn[standard] 0.17.4 depends on httptools<0.4.0 and >=0.2.0; extra == "standard"
The user requested httptools==0.6.1
[...]

I'm not the most seasoned developer but this gives me the impression that uvicorn does actually contain httptools and uvloop. Am I missing something?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.