--- /dev/null
+FROM python:3.7
+
+RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,,6.0.0"
+
+COPY ./app /app
+
+CMD ["python", "/app/main.py"]
--- /dev/null
+name: "Generate FastAPI People"
+description: "Generate the data for the FastAPI People page"
+author: "Sebastián Ramírez <tiangolo@gmail.com>"
+inputs:
+ token:
+ description: 'Token for the repo. Can be passed in using {{ secrets.GITHUB_TOKEN }}'
+ required: true
+runs:
+ using: 'docker'
+ image: 'Dockerfile'
--- /dev/null
+import logging
+import subprocess
+from collections import Counter
+from datetime import datetime, timedelta, timezone
+from pathlib import Path
+from typing import Container, Dict, List, Optional, Set
+
+import httpx
+from github import Github
+import yaml
+from pydantic import BaseModel, BaseSettings, SecretStr
+
+github_graphql_url = "https://api.github.com/graphql"
+
+issues_query = """
+query Q($after: String) {
+ repository(name: "fastapi", owner: "tiangolo") {
+ issues(first: 100, after: $after) {
+ edges {
+ cursor
+ node {
+ number
+ author {
+ login
+ avatarUrl
+ url
+ }
+ title
+ createdAt
+ state
+ comments(first: 100) {
+ nodes {
+ createdAt
+ author {
+ login
+ avatarUrl
+ url
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+"""
+
+prs_query = """
+query Q($after: String) {
+ repository(name: "fastapi", owner: "tiangolo") {
+ pullRequests(first: 100, after: $after) {
+ edges {
+ cursor
+ node {
+ number
+ labels(first: 100) {
+ nodes {
+ name
+ }
+ }
+ author {
+ login
+ avatarUrl
+ url
+ }
+ title
+ createdAt
+ state
+ comments(first: 100) {
+ nodes {
+ createdAt
+ author {
+ login
+ avatarUrl
+ url
+ }
+ }
+ }
+ reviews(first:100) {
+ nodes {
+ author {
+ login
+ avatarUrl
+ url
+ }
+ state
+ }
+ }
+ }
+ }
+ }
+ }
+}
+"""
+
+sponsors_query = """
+query Q($after: String) {
+ user(login: "tiangolo") {
+ sponsorshipsAsMaintainer(first: 100, after: $after) {
+ edges {
+ cursor
+ node {
+ sponsorEntity {
+ ... on Organization {
+ login
+ avatarUrl
+ url
+ }
+ ... on User {
+ login
+ avatarUrl
+ url
+ }
+ }
+ tier {
+ name
+ monthlyPriceInDollars
+ }
+ }
+ }
+ }
+ }
+}
+"""
+
+
+class Author(BaseModel):
+ login: str
+ avatarUrl: str
+ url: str
+
+
+class CommentsNode(BaseModel):
+ createdAt: datetime
+ author: Optional[Author] = None
+
+
+class Comments(BaseModel):
+ nodes: List[CommentsNode]
+
+
+class IssuesNode(BaseModel):
+ number: int
+ author: Optional[Author] = None
+ title: str
+ createdAt: datetime
+ state: str
+ comments: Comments
+
+
+class IssuesEdge(BaseModel):
+ cursor: str
+ node: IssuesNode
+
+
+class Issues(BaseModel):
+ edges: List[IssuesEdge]
+
+
+class IssuesRepository(BaseModel):
+ issues: Issues
+
+
+class IssuesResponseData(BaseModel):
+ repository: IssuesRepository
+
+
+class IssuesResponse(BaseModel):
+ data: IssuesResponseData
+
+
+class LabelNode(BaseModel):
+ name: str
+
+
+class Labels(BaseModel):
+ nodes: List[LabelNode]
+
+
+class ReviewNode(BaseModel):
+ author: Optional[Author] = None
+ state: str
+
+
+class Reviews(BaseModel):
+ nodes: List[ReviewNode]
+
+
+class PullRequestNode(BaseModel):
+ number: int
+ labels: Labels
+ author: Optional[Author] = None
+ title: str
+ createdAt: datetime
+ state: str
+ comments: Comments
+ reviews: Reviews
+
+
+class PullRequestEdge(BaseModel):
+ cursor: str
+ node: PullRequestNode
+
+
+class PullRequests(BaseModel):
+ edges: List[PullRequestEdge]
+
+
+class PRsRepository(BaseModel):
+ pullRequests: PullRequests
+
+
+class PRsResponseData(BaseModel):
+ repository: PRsRepository
+
+
+class PRsResponse(BaseModel):
+ data: PRsResponseData
+
+
+class SponsorEntity(BaseModel):
+ login: str
+ avatarUrl: str
+ url: str
+
+
+class Tier(BaseModel):
+ name: str
+ monthlyPriceInDollars: float
+
+
+class SponsorshipAsMaintainerNode(BaseModel):
+ sponsorEntity: SponsorEntity
+ tier: Tier
+
+
+class SponsorshipAsMaintainerEdge(BaseModel):
+ cursor: str
+ node: SponsorshipAsMaintainerNode
+
+
+class SponsorshipAsMaintainer(BaseModel):
+ edges: List[SponsorshipAsMaintainerEdge]
+
+
+class SponsorsUser(BaseModel):
+ sponsorshipsAsMaintainer: SponsorshipAsMaintainer
+
+
+class SponsorsResponseData(BaseModel):
+ user: SponsorsUser
+
+
+class SponsorsResponse(BaseModel):
+ data: SponsorsResponseData
+
+
+class Settings(BaseSettings):
+ input_token: SecretStr
+ github_repository: str
+
+
+def get_graphql_response(
+ *, settings: Settings, query: str, after: Optional[str] = None
+):
+ headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"}
+ variables = {"after": after}
+ response = httpx.post(
+ github_graphql_url,
+ headers=headers,
+ json={"query": query, "variables": variables, "operationName": "Q"},
+ )
+ if not response.status_code == 200:
+ logging.error(f"Response was not 200, after: {after}")
+ logging.error(response.text)
+ raise RuntimeError(response.text)
+ data = response.json()
+ return data
+
+
+def get_graphql_issue_edges(*, settings: Settings, after: Optional[str] = None):
+ data = get_graphql_response(settings=settings, query=issues_query, after=after)
+ graphql_response = IssuesResponse.parse_obj(data)
+ return graphql_response.data.repository.issues.edges
+
+
+def get_graphql_pr_edges(*, settings: Settings, after: Optional[str] = None):
+ data = get_graphql_response(settings=settings, query=prs_query, after=after)
+ graphql_response = PRsResponse.parse_obj(data)
+ return graphql_response.data.repository.pullRequests.edges
+
+
+def get_graphql_sponsor_edges(*, settings: Settings, after: Optional[str] = None):
+ data = get_graphql_response(settings=settings, query=sponsors_query, after=after)
+ graphql_response = SponsorsResponse.parse_obj(data)
+ return graphql_response.data.user.sponsorshipsAsMaintainer.edges
+
+
+def get_experts(settings: Settings):
+ issue_nodes: List[IssuesNode] = []
+ issue_edges = get_graphql_issue_edges(settings=settings)
+
+ while issue_edges:
+ for edge in issue_edges:
+ issue_nodes.append(edge.node)
+ last_edge = issue_edges[-1]
+ issue_edges = get_graphql_issue_edges(settings=settings, after=last_edge.cursor)
+
+ commentors = Counter()
+ last_month_commentors = Counter()
+ authors: Dict[str, Author] = {}
+
+ now = datetime.now(tz=timezone.utc)
+ one_month_ago = now - timedelta(days=30)
+
+ for issue in issue_nodes:
+ issue_author_name = None
+ if issue.author:
+ authors[issue.author.login] = issue.author
+ issue_author_name = issue.author.login
+ issue_commentors = set()
+ for comment in issue.comments.nodes:
+ if comment.author:
+ authors[comment.author.login] = comment.author
+ if comment.author.login == issue_author_name:
+ continue
+ issue_commentors.add(comment.author.login)
+ for author_name in issue_commentors:
+ commentors[author_name] += 1
+ if issue.createdAt > one_month_ago:
+ last_month_commentors[author_name] += 1
+ return commentors, last_month_commentors, authors
+
+
+def get_contributors(settings: Settings):
+ pr_nodes: List[PullRequestNode] = []
+ pr_edges = get_graphql_pr_edges(settings=settings)
+
+ while pr_edges:
+ for edge in pr_edges:
+ pr_nodes.append(edge.node)
+ last_edge = pr_edges[-1]
+ pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor)
+
+ contributors = Counter()
+ commentors = Counter()
+ reviewers = Counter()
+ authors: Dict[str, Author] = {}
+
+ for pr in pr_nodes:
+ author_name = None
+ if pr.author:
+ authors[pr.author.login] = pr.author
+ author_name = pr.author.login
+ pr_commentors: Set[str] = set()
+ pr_reviewers: Set[str] = set()
+ for comment in pr.comments.nodes:
+ if comment.author:
+ authors[comment.author.login] = comment.author
+ if comment.author.login == author_name:
+ continue
+ pr_commentors.add(comment.author.login)
+ for author_name in pr_commentors:
+ commentors[author_name] += 1
+ for review in pr.reviews.nodes:
+ if review.author:
+ authors[review.author.login] = review.author
+ pr_reviewers.add(review.author.login)
+ for reviewer in pr_reviewers:
+ reviewers[reviewer] += 1
+ if pr.state == "MERGED" and pr.author:
+ contributors[pr.author.login] += 1
+ return contributors, commentors, reviewers, authors
+
+
+def get_sponsors(settings: Settings):
+ nodes: List[SponsorshipAsMaintainerNode] = []
+ edges = get_graphql_sponsor_edges(settings=settings)
+
+ while edges:
+ for edge in edges:
+ nodes.append(edge.node)
+ last_edge = edges[-1]
+ edges = get_graphql_sponsor_edges(settings=settings, after=last_edge.cursor)
+
+ entities: Dict[str, SponsorEntity] = {}
+ for node in nodes:
+ entities[node.sponsorEntity.login] = node.sponsorEntity
+ return entities
+
+
+def get_top_users(
+ *,
+ counter: Counter,
+ min_count: int,
+ authors: Dict[str, Author],
+ skip_users: Container[str],
+):
+ users = []
+ for commentor, count in counter.most_common(50):
+ if commentor in skip_users:
+ continue
+ if count >= min_count:
+ author = authors[commentor]
+ users.append(
+ {
+ "login": commentor,
+ "count": count,
+ "avatarUrl": author.avatarUrl,
+ "url": author.url,
+ }
+ )
+ return users
+
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.INFO)
+ settings = Settings()
+ logging.info(f"Using config: {settings.json()}")
+ g = Github(settings.input_token.get_secret_value())
+ repo = g.get_repo(settings.github_repository)
+ issue_commentors, issue_last_month_commentors, issue_authors = get_experts(
+ settings=settings
+ )
+ contributors, pr_commentors, reviewers, pr_authors = get_contributors(
+ settings=settings
+ )
+ authors = {**issue_authors, **pr_authors}
+ maintainers_logins = {"tiangolo"}
+ bot_names = {"codecov", "github-actions"}
+ maintainers = []
+ for login in maintainers_logins:
+ user = authors[login]
+ maintainers.append(
+ {
+ "login": login,
+ "answers": issue_commentors[login],
+ "prs": contributors[login],
+ "avatarUrl": user.avatarUrl,
+ "url": user.url,
+ }
+ )
+
+ min_count_expert = 10
+ min_count_last_month = 3
+ min_count_contributor = 4
+ min_count_reviewer = 4
+ skip_users = maintainers_logins | bot_names
+ experts = get_top_users(
+ counter=issue_commentors,
+ min_count=min_count_expert,
+ authors=authors,
+ skip_users=skip_users,
+ )
+ last_month_active = get_top_users(
+ counter=issue_last_month_commentors,
+ min_count=min_count_last_month,
+ authors=authors,
+ skip_users=skip_users,
+ )
+ top_contributors = get_top_users(
+ counter=contributors,
+ min_count=min_count_contributor,
+ authors=authors,
+ skip_users=skip_users,
+ )
+ top_reviewers = get_top_users(
+ counter=reviewers,
+ min_count=min_count_reviewer,
+ authors=authors,
+ skip_users=skip_users,
+ )
+
+ sponsors_by_login = get_sponsors(settings=settings)
+ sponsors = []
+ for login, sponsor in sponsors_by_login.items():
+ sponsors.append(
+ {"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url}
+ )
+
+ people = {
+ "maintainers": maintainers,
+ "experts": experts,
+ "last_month_active": last_month_active,
+ "top_contributors": top_contributors,
+ "top_reviewers": top_reviewers,
+ "sponsors": sponsors,
+ }
+ people_path = Path("./docs/en/data/people.yml")
+ people_path.write_text(
+ yaml.dump(people, sort_keys=False, width=200, allow_unicode=True),
+ encoding="utf-8",
+ )
+ logging.info("Setting up GitHub Actions git user")
+ subprocess.run(["git", "config", "user.name", "github-actions"], check=True)
+ subprocess.run(["git", "config", "user.email", "github-actions@github.com"], check=True)
+ branch_name = "fastapi-people"
+ logging.info(f"Creating a new branch {branch_name}")
+ subprocess.run(["git", "checkout", "-b", branch_name], check=True)
+ logging.info("Adding updated file")
+ subprocess.run(["git", "add", str(people_path)], check=True)
+ logging.info("Committing updated file")
+ message = "👥 Update FastAPI People"
+ result = subprocess.run(["git", "commit", "-m", message], check=True)
+ logging.info("Pushing branch")
+ subprocess.run(["git", "push", "origin", branch_name], check=True)
+ logging.info("Creating PR")
+ pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
+ logging.info(f"Created PR: {pr.number}")
+ logging.info("Finished")
--- /dev/null
+name: FastAPI People
+
+on:
+ schedule:
+ - cron: "30 * * * *"
+ workflow_dispatch:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: ./.github/actions/people
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
--- /dev/null
+maintainers:
+- login: tiangolo
+ answers: 979
+ prs: 177
+ avatarUrl: https://avatars1.githubusercontent.com/u/1326112?u=05f95ca7fdead36edd9c86be46b4ef6c3c71f876&v=4
+ url: https://github.com/tiangolo
+experts:
+- login: dmontagu
+ count: 262
+ avatarUrl: https://avatars2.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
+ url: https://github.com/dmontagu
+- login: euri10
+ count: 166
+ avatarUrl: https://avatars3.githubusercontent.com/u/1104190?u=ffd411da5d3b7ad3aa18261317f7ddc76f763c33&v=4
+ url: https://github.com/euri10
+- login: phy25
+ count: 129
+ avatarUrl: https://avatars0.githubusercontent.com/u/331403?v=4
+ url: https://github.com/phy25
+- login: Kludex
+ count: 93
+ avatarUrl: https://avatars1.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
+ url: https://github.com/Kludex
+- login: sm-Fifteen
+ count: 39
+ avatarUrl: https://avatars0.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4
+ url: https://github.com/sm-Fifteen
+- login: prostomarkeloff
+ count: 33
+ avatarUrl: https://avatars3.githubusercontent.com/u/28061158?u=72309cc1f2e04e40fa38b29969cb4e9d3f722e7b&v=4
+ url: https://github.com/prostomarkeloff
+- login: ycd
+ count: 33
+ avatarUrl: https://avatars2.githubusercontent.com/u/62724709?u=496a800351ea1009678e40b26288a2a6c0dfa8bd&v=4
+ url: https://github.com/ycd
+- login: ArcLightSlavik
+ count: 30
+ avatarUrl: https://avatars3.githubusercontent.com/u/31127044?u=b81d0c33b056152513fb14749a9fe00f39887a8e&v=4
+ url: https://github.com/ArcLightSlavik
+- login: wshayes
+ count: 29
+ avatarUrl: https://avatars2.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
+ url: https://github.com/wshayes
+- login: Mause
+ count: 28
+ avatarUrl: https://avatars2.githubusercontent.com/u/1405026?v=4
+ url: https://github.com/Mause
+- login: dbanty
+ count: 25
+ avatarUrl: https://avatars2.githubusercontent.com/u/43723790?u=0cf33e4f40efc2ea206a1189fd63a11344eb88ed&v=4
+ url: https://github.com/dbanty
+- login: nsidnev
+ count: 22
+ avatarUrl: https://avatars0.githubusercontent.com/u/22559461?u=a9cc3238217e21dc8796a1a500f01b722adb082c&v=4
+ url: https://github.com/nsidnev
+- login: chris-allnutt
+ count: 21
+ avatarUrl: https://avatars0.githubusercontent.com/u/565544?v=4
+ url: https://github.com/chris-allnutt
+- login: Dustyposa
+ count: 21
+ avatarUrl: https://avatars0.githubusercontent.com/u/27180793?u=5cf2877f50b3eb2bc55086089a78a36f07042889&v=4
+ url: https://github.com/Dustyposa
+- login: acnebs
+ count: 19
+ avatarUrl: https://avatars2.githubusercontent.com/u/9054108?u=bfd127b3e6200f4d00afd714f0fc95c2512df19b&v=4
+ url: https://github.com/acnebs
+- login: retnikt
+ count: 19
+ avatarUrl: https://avatars1.githubusercontent.com/u/24581770?v=4
+ url: https://github.com/retnikt
+- login: SirTelemak
+ count: 19
+ avatarUrl: https://avatars1.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4
+ url: https://github.com/SirTelemak
+- login: jorgerpo
+ count: 17
+ avatarUrl: https://avatars1.githubusercontent.com/u/12537771?u=7444d20019198e34911082780cc7ad73f2b97cb3&v=4
+ url: https://github.com/jorgerpo
+- login: raphaelauv
+ count: 17
+ avatarUrl: https://avatars3.githubusercontent.com/u/10202690?u=e6f86f5c0c3026a15d6b51792fa3e532b12f1371&v=4
+ url: https://github.com/raphaelauv
+- login: Slyfoxy
+ count: 17
+ avatarUrl: https://avatars1.githubusercontent.com/u/28262306?u=66ee21316275ef356081c2efc4ed7a4572e690dc&v=4
+ url: https://github.com/Slyfoxy
+- login: haizaar
+ count: 13
+ avatarUrl: https://avatars3.githubusercontent.com/u/58201?u=4f1f9843d69433ca0d380d95146cfe119e5fdac4&v=4
+ url: https://github.com/haizaar
+- login: zamiramir
+ count: 11
+ avatarUrl: https://avatars1.githubusercontent.com/u/40475662?u=e58ef61034e8d0d6a312cc956fb09b9c3332b449&v=4
+ url: https://github.com/zamiramir
+- login: stefanondisponibile
+ count: 10
+ avatarUrl: https://avatars1.githubusercontent.com/u/20441825?u=ee1e59446b98f8ec2363caeda4c17164d0d9cc7d&v=4
+ url: https://github.com/stefanondisponibile
+last_month_active:
+- login: Mause
+ count: 20
+ avatarUrl: https://avatars2.githubusercontent.com/u/1405026?v=4
+ url: https://github.com/Mause
+- login: ycd
+ count: 8
+ avatarUrl: https://avatars2.githubusercontent.com/u/62724709?u=496a800351ea1009678e40b26288a2a6c0dfa8bd&v=4
+ url: https://github.com/ycd
+- login: Kludex
+ count: 7
+ avatarUrl: https://avatars1.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
+ url: https://github.com/Kludex
+- login: ArcLightSlavik
+ count: 6
+ avatarUrl: https://avatars3.githubusercontent.com/u/31127044?u=b81d0c33b056152513fb14749a9fe00f39887a8e&v=4
+ url: https://github.com/ArcLightSlavik
+- login: SebastianLuebke
+ count: 4
+ avatarUrl: https://avatars3.githubusercontent.com/u/21161532?u=ba033c1bf6851b874cfa05a8a824b9f1ff434c37&v=4
+ url: https://github.com/SebastianLuebke
+- login: includeamin
+ count: 3
+ avatarUrl: https://avatars1.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4
+ url: https://github.com/includeamin
+top_contributors:
+- login: dmontagu
+ count: 16
+ avatarUrl: https://avatars2.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
+ url: https://github.com/dmontagu
+- login: waynerv
+ count: 16
+ avatarUrl: https://avatars3.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
+ url: https://github.com/waynerv
+- login: euri10
+ count: 13
+ avatarUrl: https://avatars3.githubusercontent.com/u/1104190?u=ffd411da5d3b7ad3aa18261317f7ddc76f763c33&v=4
+ url: https://github.com/euri10
+- login: tokusumi
+ count: 10
+ avatarUrl: https://avatars0.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
+ url: https://github.com/tokusumi
+- login: mariacamilagl
+ count: 7
+ avatarUrl: https://avatars2.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
+ url: https://github.com/mariacamilagl
+- login: Serrones
+ count: 6
+ avatarUrl: https://avatars3.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
+ url: https://github.com/Serrones
+- login: wshayes
+ count: 5
+ avatarUrl: https://avatars2.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
+ url: https://github.com/wshayes
+- login: jekirl
+ count: 4
+ avatarUrl: https://avatars3.githubusercontent.com/u/2546697?v=4
+ url: https://github.com/jekirl
+top_reviewers:
+- login: Kludex
+ count: 47
+ avatarUrl: https://avatars1.githubusercontent.com/u/7353520?u=cf8455cb899806b774a3a71073f88583adec99f6&v=4
+ url: https://github.com/Kludex
+- login: tokusumi
+ count: 37
+ avatarUrl: https://avatars0.githubusercontent.com/u/41147016?u=55010621aece725aa702270b54fed829b6a1fe60&v=4
+ url: https://github.com/tokusumi
+- login: dmontagu
+ count: 23
+ avatarUrl: https://avatars2.githubusercontent.com/u/35119617?u=58ed2a45798a4339700e2f62b2e12e6e54bf0396&v=4
+ url: https://github.com/dmontagu
+- login: cassiobotaro
+ count: 14
+ avatarUrl: https://avatars2.githubusercontent.com/u/3127847?u=b0a652331da17efeb85cd6e3a4969182e5004804&v=4
+ url: https://github.com/cassiobotaro
+- login: AdrianDeAnda
+ count: 13
+ avatarUrl: https://avatars0.githubusercontent.com/u/1024932?u=bb7f8a0d6c9de4e9d0320a9f271210206e202250&v=4
+ url: https://github.com/AdrianDeAnda
+- login: Laineyzhang55
+ count: 12
+ avatarUrl: https://avatars0.githubusercontent.com/u/59285379?v=4
+ url: https://github.com/Laineyzhang55
+- login: yanever
+ count: 11
+ avatarUrl: https://avatars2.githubusercontent.com/u/21978760?v=4
+ url: https://github.com/yanever
+- login: SwftAlpc
+ count: 11
+ avatarUrl: https://avatars1.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4
+ url: https://github.com/SwftAlpc
+- login: ycd
+ count: 11
+ avatarUrl: https://avatars2.githubusercontent.com/u/62724709?u=496a800351ea1009678e40b26288a2a6c0dfa8bd&v=4
+ url: https://github.com/ycd
+- login: waynerv
+ count: 10
+ avatarUrl: https://avatars3.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4
+ url: https://github.com/waynerv
+- login: mariacamilagl
+ count: 10
+ avatarUrl: https://avatars2.githubusercontent.com/u/11489395?u=4adb6986bf3debfc2b8216ae701f2bd47d73da7d&v=4
+ url: https://github.com/mariacamilagl
+- login: Attsun1031
+ count: 10
+ avatarUrl: https://avatars2.githubusercontent.com/u/1175560?v=4
+ url: https://github.com/Attsun1031
+- login: RunningIkkyu
+ count: 9
+ avatarUrl: https://avatars0.githubusercontent.com/u/31848542?u=706e1ee3f248245f2d68b976d149d06fd5a2010d&v=4
+ url: https://github.com/RunningIkkyu
+- login: komtaki
+ count: 9
+ avatarUrl: https://avatars1.githubusercontent.com/u/39375566?v=4
+ url: https://github.com/komtaki
+- login: Serrones
+ count: 7
+ avatarUrl: https://avatars3.githubusercontent.com/u/22691749?u=4795b880e13ca33a73e52fc0ef7dc9c60c8fce47&v=4
+ url: https://github.com/Serrones
+- login: ryuckel
+ count: 7
+ avatarUrl: https://avatars1.githubusercontent.com/u/36391432?u=094eec0cfddd5013f76f31e55e56147d78b19553&v=4
+ url: https://github.com/ryuckel
+- login: MashhadiNima
+ count: 5
+ avatarUrl: https://avatars0.githubusercontent.com/u/49960770?u=e39b11d47188744ee07b2a1c7ce1a1bdf3c80760&v=4
+ url: https://github.com/MashhadiNima
+- login: euri10
+ count: 4
+ avatarUrl: https://avatars3.githubusercontent.com/u/1104190?u=ffd411da5d3b7ad3aa18261317f7ddc76f763c33&v=4
+ url: https://github.com/euri10
+- login: rkbeatss
+ count: 4
+ avatarUrl: https://avatars0.githubusercontent.com/u/23391143?u=56ab6bff50be950fa8cae5cf736f2ae66e319ff3&v=4
+ url: https://github.com/rkbeatss
+sponsors:
+- login: samuelcolvin
+ avatarUrl: https://avatars3.githubusercontent.com/u/4039449?u=807390ba9cfe23906c3bf8a0d56aaca3cf2bfa0d&v=4
+ url: https://github.com/samuelcolvin
+- login: mkeen
+ avatarUrl: https://avatars3.githubusercontent.com/u/38221?u=03e076e08a10a4de0d48a348f1aab0223c5cf24a&v=4
+ url: https://github.com/mkeen
+- login: wshayes
+ avatarUrl: https://avatars2.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
+ url: https://github.com/wshayes
+- login: ltieman
+ avatarUrl: https://avatars1.githubusercontent.com/u/1084689?u=c9bf77f5e57f98b49694870219b9bd9d1cc862e7&v=4
+ url: https://github.com/ltieman
+- login: mrmattwright
+ avatarUrl: https://avatars3.githubusercontent.com/u/1277725?v=4
+ url: https://github.com/mrmattwright
+- login: timdrijvers
+ avatarUrl: https://avatars1.githubusercontent.com/u/1694939?v=4
+ url: https://github.com/timdrijvers
+- login: abdelhai
+ avatarUrl: https://avatars3.githubusercontent.com/u/1752577?u=8f8f2bce75f3ab68188cea2b5da37c784197acd8&v=4
+ url: https://github.com/abdelhai
+- login: ddahan
+ avatarUrl: https://avatars0.githubusercontent.com/u/1933516?u=4068dc3c5db5d3605116c4f5df6deb9fee324c33&v=4
+ url: https://github.com/ddahan
+- login: cbonoz
+ avatarUrl: https://avatars0.githubusercontent.com/u/2351087?u=fd3e8030b2cc9fbfbb54a65e9890c548a016f58b&v=4
+ url: https://github.com/cbonoz
+- login: mrgnw
+ avatarUrl: https://avatars3.githubusercontent.com/u/2504532?u=7ec43837a6d0afa80f96f0788744ea6341b89f97&v=4
+ url: https://github.com/mrgnw
+- login: paul121
+ avatarUrl: https://avatars2.githubusercontent.com/u/3116995?u=6e2d8691cc345e63ee02e4eb4d7cef82b1fcbedc&v=4
+ url: https://github.com/paul121
+- login: andre1sk
+ avatarUrl: https://avatars1.githubusercontent.com/u/3148093?v=4
+ url: https://github.com/andre1sk
+- login: igorcorrea
+ avatarUrl: https://avatars0.githubusercontent.com/u/3438238?u=c57605077c31a8f7b2341fc4912507f91b4a5621&v=4
+ url: https://github.com/igorcorrea
+- login: pawamoy
+ avatarUrl: https://avatars2.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
+ url: https://github.com/pawamoy
+- login: p141592
+ avatarUrl: https://avatars3.githubusercontent.com/u/5256328?u=7f9fdf3329bf90017cff00c8a78781bd7a2b48aa&v=4
+ url: https://github.com/p141592
+- login: fabboe
+ avatarUrl: https://avatars3.githubusercontent.com/u/7251331?v=4
+ url: https://github.com/fabboe
+- login: macleodmac
+ avatarUrl: https://avatars2.githubusercontent.com/u/8996312?u=e39c68c3e0b1d264dcba4850134a291680f46355&v=4
+ url: https://github.com/macleodmac
+- login: cristeaadrian
+ avatarUrl: https://avatars0.githubusercontent.com/u/9112724?u=76099d546d6ee44b3ad7269773ecb916590c6a36&v=4
+ url: https://github.com/cristeaadrian
+- login: iambobmae
+ avatarUrl: https://avatars2.githubusercontent.com/u/12390270?u=c9a35c2ee5092a9b4135ebb1f91b7f521c467031&v=4
+ url: https://github.com/iambobmae
+- login: Cozmo25
+ avatarUrl: https://avatars1.githubusercontent.com/u/12619962?u=679dcd6785121e14f6254e9dd0961baf3b1fef5d&v=4
+ url: https://github.com/Cozmo25
+- login: augustogoulart
+ avatarUrl: https://avatars3.githubusercontent.com/u/13952931?u=9326220a94c303c21dc0da56f1f2ff3c10ed591f&v=4
+ url: https://github.com/augustogoulart
+- login: la-mar
+ avatarUrl: https://avatars1.githubusercontent.com/u/16618300?u=7755c0521d2bb0d704f35a51464b15c1e2e6c4da&v=4
+ url: https://github.com/la-mar
+- login: robintully
+ avatarUrl: https://avatars2.githubusercontent.com/u/17059673?u=862b9bb01513f5acd30df97433cb97a24dbfb772&v=4
+ url: https://github.com/robintully
+- login: wedwardbeck
+ avatarUrl: https://avatars3.githubusercontent.com/u/19333237?u=1de4ae2bf8d59eb4c013f21d863cbe0f2010575f&v=4
+ url: https://github.com/wedwardbeck
+- login: linusg
+ avatarUrl: https://avatars3.githubusercontent.com/u/19366641?u=125e390abef8fff3b3b0d370c369cba5d7fd4c67&v=4
+ url: https://github.com/linusg
+- login: SebastianLuebke
+ avatarUrl: https://avatars3.githubusercontent.com/u/21161532?u=ba033c1bf6851b874cfa05a8a824b9f1ff434c37&v=4
+ url: https://github.com/SebastianLuebke
+- login: raminsj13
+ avatarUrl: https://avatars2.githubusercontent.com/u/24259406?u=d51f2a526312ebba150a06936ed187ca0727d329&v=4
+ url: https://github.com/raminsj13
+- login: mertguvencli
+ avatarUrl: https://avatars3.githubusercontent.com/u/29762151?u=16a906d90df96c8cff9ea131a575c4bc171b1523&v=4
+ url: https://github.com/mertguvencli
+- login: orihomie
+ avatarUrl: https://avatars3.githubusercontent.com/u/29889683?u=6bc2135a52fcb3a49e69e7d50190796618185fda&v=4
+ url: https://github.com/orihomie
+- login: dcooper01
+ avatarUrl: https://avatars2.githubusercontent.com/u/32238294?u=2a83c78b7f2a5f97beeede0b604bbe44cd21b46b&v=4
+ url: https://github.com/dcooper01
+- login: d3vzer0
+ avatarUrl: https://avatars3.githubusercontent.com/u/34250156?u=c50c9df0e34f411f7e5f050a72e8d89696284eba&v=4
+ url: https://github.com/d3vzer0
+- login: dbanty
+ avatarUrl: https://avatars2.githubusercontent.com/u/43723790?u=0cf33e4f40efc2ea206a1189fd63a11344eb88ed&v=4
+ url: https://github.com/dbanty
+- login: Brontomerus
+ avatarUrl: https://avatars0.githubusercontent.com/u/61284158?u=c00d807195815014d0b6597b3801ee9c494802dd&v=4
+ url: https://github.com/Brontomerus
+- login: primer-api
+ avatarUrl: https://avatars2.githubusercontent.com/u/62152773?u=4549d79b0ad1d30ecfbef6c6933593e90e819c75&v=4
+ url: https://github.com/primer-api
+- login: daverin
+ avatarUrl: https://avatars1.githubusercontent.com/u/70378377?u=6d1814195c0de7162820eaad95a25b423a3869c0&v=4
+ url: https://github.com/daverin
a.external-link::after {
- /* \00A0 is a non-breaking space
+ /* \00A0 is a non-breaking space
to make the mark be on the same line as the link
*/
- content: "\00A0[↪]";
+ content: "\00A0[↪]";
}
a.internal-link::after {
- /* \00A0 is a non-breaking space
+ /* \00A0 is a non-breaking space
to make the mark be on the same line as the link
*/
- content: "\00A0↪";
+ content: "\00A0↪";
}
/* Give space to lower icons so Gitter chat doesn't get on top of them */
.md-footer-meta {
- padding-bottom: 2em;
+ padding-bottom: 2em;
+}
+
+.user-list {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.user-list-center {
+ justify-content: space-evenly;
+}
+
+.user {
+ margin: 1em;
+ min-width: 7em;
+}
+
+.user .avatar-wrapper {
+ width: 80px;
+ height: 80px;
+ margin: 10px auto;
+ overflow: hidden;
+ border-radius: 50%;
+ position: relative;
+}
+
+.user .avatar-wrapper img {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.user .title {
+ text-align: center;
+}
+
+.user .count {
+ font-size: 80%;
+ text-align: center;
}
--- /dev/null
+# FastAPI People
+
+FastAPI has an amazing community that welcomes people from all backgrounds.
+
+## Creator - Maintainer
+
+Hey! 👋
+
+This is me:
+
+{% if people %}
+<div class="user-list user-list-center">
+{% for user in people.maintainers %}
+
+<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Answers: {{ user.answers }}</div><div class="count">Pull Requests: {{ user.prs }}</div></div>
+{% endfor %}
+
+</div>
+{% endif %}
+
+I'm the creator and maintainer of **FastAPI**. You can read more about that in [Help FastAPI - Get Help - Connect with the author](help-fastapi.md#connect-with-the-author){.internal-link target=_blank}.
+
+...But here I want to show you the community.
+
+---
+
+**FastAPI** receives a lot of support from the community. And I want to highlight their contributions.
+
+These are the people that:
+
+* [Help others with issues (questions) in GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank}.
+* [Create Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank}.
+* Review Pull Requests, [especially important for translations](contributing.md#translations){.internal-link target=_blank}.
+
+A round of applause to them. 👏 🙇
+
+## Most active users last month
+
+These are the users that have been [helping others the most with issues (questions) in GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} during the last month. ☕
+
+{% if people %}
+<div class="user-list user-list-center">
+{% for user in people.last_month_active %}
+
+<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Issues replied: {{ user.count }}</div></div>
+{% endfor %}
+
+</div>
+{% endif %}
+
+## Experts
+
+Here are the **FastAPI Experts**. 🤓
+
+These are the users that have [helped others the most with issues (questions) in GitHub](help-fastapi.md#help-others-with-issues-in-github){.internal-link target=_blank} through *all time*.
+
+They have proven to be experts by helping many others. ✨
+
+{% if people %}
+<div class="user-list user-list-center">
+{% for user in people.experts %}
+
+<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Issues replied: {{ user.count }}</div></div>
+{% endfor %}
+
+</div>
+{% endif %}
+
+## Top Contributors
+
+Here are the **Top Contributors**. 👷
+
+These users have [created the most Pull Requests](help-fastapi.md#create-a-pull-request){.internal-link target=_blank} that have been *merged*.
+
+They have contributed source code, documentation, translations, etc. 📦
+
+{% if people %}
+<div class="user-list user-list-center">
+{% for user in people.top_contributors %}
+
+<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Pull Requests: {{ user.count }}</div></div>
+{% endfor %}
+
+</div>
+{% endif %}
+
+There are many other contributors (more than a hundred), you can see them all in the <a href="https://github.com/tiangolo/fastapi/graphs/contributors" class="external-link" target="_blank">FastAPI GitHub Contributors page</a>. 👷
+
+## Top Reviewers
+
+These users are the **Top Reviewers**. 🕵️
+
+### Reviews for Translations
+
+I only speak a few languages (and not very well 😅). So, the reviewers are the ones that have the [**power to approve translations**](contributing.md#translations){.internal-link target=_blank} of the documentation. Without them, there wouldn't be documentation in several other languages.
+
+---
+
+The **Top Reviewers** 🕵️ have reviewed the most Pull Requests from others, ensuring the quality of the code, documentation, and especially, the **translations**.
+
+{% if people %}
+<div class="user-list user-list-center">
+{% for user in people.top_reviewers %}
+
+<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Reviews: {{ user.count }}</div></div>
+{% endfor %}
+
+</div>
+{% endif %}
+
+## Sponsors
+
+These are the **Sponsors**. 😎
+
+They are supporting my work with **FastAPI** (and others) through <a href="https://github.com/sponsors/tiangolo" class="external-link" target="_blank">GitHub Sponsors</a>.
+
+{% if people %}
+<div class="user-list user-list-center">
+{% for user in people.sponsors %}
+
+<div class="user"><a href="{{ user.url }}" target="_blank"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a></div>
+{% endfor %}
+
+</div>
+{% endif %}
+
+## About the data - technical details
+
+The intention of this page is to highlight the effort of the community to help others.
+
+Especially including efforts that are normally less visible, and in many cases more arduous, like helping others with issues and reviewing Pull Requests with translations.
+
+The data is calculated each month, you can read the <a href="https://github.com/tiangolo/fastapi/blob/master/.github/actions/people/app/main.py" class="external-link" target="_blank">source code here</a>.
+
+I also reserve the right to update the algorithm, sections, thresholds, etc (just in case 🤷).
- uk: /uk/
- zh: /zh/
- features.md
+- fastapi-people.md
- python-types.md
- Tutorial - User Guide:
- tutorial/index.md