committed by
GitHub
1280 changed files with 54952 additions and 74765 deletions
@ -1,7 +0,0 @@ |
|||||
FROM python:3.9 |
|
||||
|
|
||||
RUN pip install httpx PyGithub "pydantic==1.5.1" "pyyaml>=5.3.1,<6.0.0" |
|
||||
|
|
||||
COPY ./app /app |
|
||||
|
|
||||
CMD ["python", "/app/main.py"] |
|
@ -1,10 +0,0 @@ |
|||||
name: "Notify Translations" |
|
||||
description: "Notify in the issue for a translation when there's a new PR available" |
|
||||
author: "Sebastián Ramírez <[email protected]>" |
|
||||
inputs: |
|
||||
token: |
|
||||
description: 'Token, to read the GitHub API. Can be passed in using {{ secrets.GITHUB_TOKEN }}' |
|
||||
required: true |
|
||||
runs: |
|
||||
using: 'docker' |
|
||||
image: 'Dockerfile' |
|
@ -1,7 +0,0 @@ |
|||||
FROM python:3.9 |
|
||||
|
|
||||
RUN pip install httpx PyGithub "pydantic==2.0.2" pydantic-settings "pyyaml>=5.3.1,<6.0.0" |
|
||||
|
|
||||
COPY ./app /app |
|
||||
|
|
||||
CMD ["python", "/app/main.py"] |
|
@ -1,10 +0,0 @@ |
|||||
name: "Generate FastAPI People" |
|
||||
description: "Generate the data for the FastAPI People page" |
|
||||
author: "Sebastián Ramírez <[email protected]>" |
|
||||
inputs: |
|
||||
token: |
|
||||
description: 'User token, to read the GitHub API. Can be passed in using {{ secrets.FASTAPI_PEOPLE }}' |
|
||||
required: true |
|
||||
runs: |
|
||||
using: 'docker' |
|
||||
image: 'Dockerfile' |
|
@ -1,682 +0,0 @@ |
|||||
import logging |
|
||||
import subprocess |
|
||||
import sys |
|
||||
from collections import Counter, defaultdict |
|
||||
from datetime import datetime, timedelta, timezone |
|
||||
from pathlib import Path |
|
||||
from typing import Any, Container, DefaultDict, Dict, List, Set, Union |
|
||||
|
|
||||
import httpx |
|
||||
import yaml |
|
||||
from github import Github |
|
||||
from pydantic import BaseModel, SecretStr |
|
||||
from pydantic_settings import BaseSettings |
|
||||
|
|
||||
github_graphql_url = "https://api.github.com/graphql" |
|
||||
questions_category_id = "MDE4OkRpc2N1c3Npb25DYXRlZ29yeTMyMDAxNDM0" |
|
||||
|
|
||||
discussions_query = """ |
|
||||
query Q($after: String, $category_id: ID) { |
|
||||
repository(name: "fastapi", owner: "fastapi") { |
|
||||
discussions(first: 100, after: $after, categoryId: $category_id) { |
|
||||
edges { |
|
||||
cursor |
|
||||
node { |
|
||||
number |
|
||||
author { |
|
||||
login |
|
||||
avatarUrl |
|
||||
url |
|
||||
} |
|
||||
title |
|
||||
createdAt |
|
||||
comments(first: 100) { |
|
||||
nodes { |
|
||||
createdAt |
|
||||
author { |
|
||||
login |
|
||||
avatarUrl |
|
||||
url |
|
||||
} |
|
||||
isAnswer |
|
||||
replies(first: 10) { |
|
||||
nodes { |
|
||||
createdAt |
|
||||
author { |
|
||||
login |
|
||||
avatarUrl |
|
||||
url |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
""" |
|
||||
|
|
||||
|
|
||||
prs_query = """ |
|
||||
query Q($after: String) { |
|
||||
repository(name: "fastapi", owner: "fastapi") { |
|
||||
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: "fastapi") { |
|
||||
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 |
|
||||
|
|
||||
|
|
||||
# Discussions |
|
||||
|
|
||||
|
|
||||
class CommentsNode(BaseModel): |
|
||||
createdAt: datetime |
|
||||
author: Union[Author, None] = None |
|
||||
|
|
||||
|
|
||||
class Replies(BaseModel): |
|
||||
nodes: List[CommentsNode] |
|
||||
|
|
||||
|
|
||||
class DiscussionsCommentsNode(CommentsNode): |
|
||||
replies: Replies |
|
||||
|
|
||||
|
|
||||
class Comments(BaseModel): |
|
||||
nodes: List[CommentsNode] |
|
||||
|
|
||||
|
|
||||
class DiscussionsComments(BaseModel): |
|
||||
nodes: List[DiscussionsCommentsNode] |
|
||||
|
|
||||
|
|
||||
class DiscussionsNode(BaseModel): |
|
||||
number: int |
|
||||
author: Union[Author, None] = None |
|
||||
title: str |
|
||||
createdAt: datetime |
|
||||
comments: DiscussionsComments |
|
||||
|
|
||||
|
|
||||
class DiscussionsEdge(BaseModel): |
|
||||
cursor: str |
|
||||
node: DiscussionsNode |
|
||||
|
|
||||
|
|
||||
class Discussions(BaseModel): |
|
||||
edges: List[DiscussionsEdge] |
|
||||
|
|
||||
|
|
||||
class DiscussionsRepository(BaseModel): |
|
||||
discussions: Discussions |
|
||||
|
|
||||
|
|
||||
class DiscussionsResponseData(BaseModel): |
|
||||
repository: DiscussionsRepository |
|
||||
|
|
||||
|
|
||||
class DiscussionsResponse(BaseModel): |
|
||||
data: DiscussionsResponseData |
|
||||
|
|
||||
|
|
||||
# PRs |
|
||||
|
|
||||
|
|
||||
class LabelNode(BaseModel): |
|
||||
name: str |
|
||||
|
|
||||
|
|
||||
class Labels(BaseModel): |
|
||||
nodes: List[LabelNode] |
|
||||
|
|
||||
|
|
||||
class ReviewNode(BaseModel): |
|
||||
author: Union[Author, None] = None |
|
||||
state: str |
|
||||
|
|
||||
|
|
||||
class Reviews(BaseModel): |
|
||||
nodes: List[ReviewNode] |
|
||||
|
|
||||
|
|
||||
class PullRequestNode(BaseModel): |
|
||||
number: int |
|
||||
labels: Labels |
|
||||
author: Union[Author, None] = 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 |
|
||||
|
|
||||
|
|
||||
# Sponsors |
|
||||
|
|
||||
|
|
||||
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 |
|
||||
httpx_timeout: int = 30 |
|
||||
|
|
||||
|
|
||||
def get_graphql_response( |
|
||||
*, |
|
||||
settings: Settings, |
|
||||
query: str, |
|
||||
after: Union[str, None] = None, |
|
||||
category_id: Union[str, None] = None, |
|
||||
) -> Dict[str, Any]: |
|
||||
headers = {"Authorization": f"token {settings.input_token.get_secret_value()}"} |
|
||||
# category_id is only used by one query, but GraphQL allows unused variables, so |
|
||||
# keep it here for simplicity |
|
||||
variables = {"after": after, "category_id": category_id} |
|
||||
response = httpx.post( |
|
||||
github_graphql_url, |
|
||||
headers=headers, |
|
||||
timeout=settings.httpx_timeout, |
|
||||
json={"query": query, "variables": variables, "operationName": "Q"}, |
|
||||
) |
|
||||
if response.status_code != 200: |
|
||||
logging.error( |
|
||||
f"Response was not 200, after: {after}, category_id: {category_id}" |
|
||||
) |
|
||||
logging.error(response.text) |
|
||||
raise RuntimeError(response.text) |
|
||||
data = response.json() |
|
||||
if "errors" in data: |
|
||||
logging.error(f"Errors in response, after: {after}, category_id: {category_id}") |
|
||||
logging.error(data["errors"]) |
|
||||
logging.error(response.text) |
|
||||
raise RuntimeError(response.text) |
|
||||
return data |
|
||||
|
|
||||
|
|
||||
def get_graphql_question_discussion_edges( |
|
||||
*, |
|
||||
settings: Settings, |
|
||||
after: Union[str, None] = None, |
|
||||
): |
|
||||
data = get_graphql_response( |
|
||||
settings=settings, |
|
||||
query=discussions_query, |
|
||||
after=after, |
|
||||
category_id=questions_category_id, |
|
||||
) |
|
||||
graphql_response = DiscussionsResponse.model_validate(data) |
|
||||
return graphql_response.data.repository.discussions.edges |
|
||||
|
|
||||
|
|
||||
def get_graphql_pr_edges(*, settings: Settings, after: Union[str, None] = None): |
|
||||
data = get_graphql_response(settings=settings, query=prs_query, after=after) |
|
||||
graphql_response = PRsResponse.model_validate(data) |
|
||||
return graphql_response.data.repository.pullRequests.edges |
|
||||
|
|
||||
|
|
||||
def get_graphql_sponsor_edges(*, settings: Settings, after: Union[str, None] = None): |
|
||||
data = get_graphql_response(settings=settings, query=sponsors_query, after=after) |
|
||||
graphql_response = SponsorsResponse.model_validate(data) |
|
||||
return graphql_response.data.user.sponsorshipsAsMaintainer.edges |
|
||||
|
|
||||
|
|
||||
class DiscussionExpertsResults(BaseModel): |
|
||||
commenters: Counter |
|
||||
last_month_commenters: Counter |
|
||||
three_months_commenters: Counter |
|
||||
six_months_commenters: Counter |
|
||||
one_year_commenters: Counter |
|
||||
authors: Dict[str, Author] |
|
||||
|
|
||||
|
|
||||
def get_discussion_nodes(settings: Settings) -> List[DiscussionsNode]: |
|
||||
discussion_nodes: List[DiscussionsNode] = [] |
|
||||
discussion_edges = get_graphql_question_discussion_edges(settings=settings) |
|
||||
|
|
||||
while discussion_edges: |
|
||||
for discussion_edge in discussion_edges: |
|
||||
discussion_nodes.append(discussion_edge.node) |
|
||||
last_edge = discussion_edges[-1] |
|
||||
discussion_edges = get_graphql_question_discussion_edges( |
|
||||
settings=settings, after=last_edge.cursor |
|
||||
) |
|
||||
return discussion_nodes |
|
||||
|
|
||||
|
|
||||
def get_discussions_experts( |
|
||||
discussion_nodes: List[DiscussionsNode], |
|
||||
) -> DiscussionExpertsResults: |
|
||||
commenters = Counter() |
|
||||
last_month_commenters = Counter() |
|
||||
three_months_commenters = Counter() |
|
||||
six_months_commenters = Counter() |
|
||||
one_year_commenters = Counter() |
|
||||
authors: Dict[str, Author] = {} |
|
||||
|
|
||||
now = datetime.now(tz=timezone.utc) |
|
||||
one_month_ago = now - timedelta(days=30) |
|
||||
three_months_ago = now - timedelta(days=90) |
|
||||
six_months_ago = now - timedelta(days=180) |
|
||||
one_year_ago = now - timedelta(days=365) |
|
||||
|
|
||||
for discussion in discussion_nodes: |
|
||||
discussion_author_name = None |
|
||||
if discussion.author: |
|
||||
authors[discussion.author.login] = discussion.author |
|
||||
discussion_author_name = discussion.author.login |
|
||||
discussion_commentors: dict[str, datetime] = {} |
|
||||
for comment in discussion.comments.nodes: |
|
||||
if comment.author: |
|
||||
authors[comment.author.login] = comment.author |
|
||||
if comment.author.login != discussion_author_name: |
|
||||
author_time = discussion_commentors.get( |
|
||||
comment.author.login, comment.createdAt |
|
||||
) |
|
||||
discussion_commentors[comment.author.login] = max( |
|
||||
author_time, comment.createdAt |
|
||||
) |
|
||||
for reply in comment.replies.nodes: |
|
||||
if reply.author: |
|
||||
authors[reply.author.login] = reply.author |
|
||||
if reply.author.login != discussion_author_name: |
|
||||
author_time = discussion_commentors.get( |
|
||||
reply.author.login, reply.createdAt |
|
||||
) |
|
||||
discussion_commentors[reply.author.login] = max( |
|
||||
author_time, reply.createdAt |
|
||||
) |
|
||||
for author_name, author_time in discussion_commentors.items(): |
|
||||
commenters[author_name] += 1 |
|
||||
if author_time > one_month_ago: |
|
||||
last_month_commenters[author_name] += 1 |
|
||||
if author_time > three_months_ago: |
|
||||
three_months_commenters[author_name] += 1 |
|
||||
if author_time > six_months_ago: |
|
||||
six_months_commenters[author_name] += 1 |
|
||||
if author_time > one_year_ago: |
|
||||
one_year_commenters[author_name] += 1 |
|
||||
discussion_experts_results = DiscussionExpertsResults( |
|
||||
authors=authors, |
|
||||
commenters=commenters, |
|
||||
last_month_commenters=last_month_commenters, |
|
||||
three_months_commenters=three_months_commenters, |
|
||||
six_months_commenters=six_months_commenters, |
|
||||
one_year_commenters=one_year_commenters, |
|
||||
) |
|
||||
return discussion_experts_results |
|
||||
|
|
||||
|
|
||||
def get_pr_nodes(settings: Settings) -> List[PullRequestNode]: |
|
||||
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) |
|
||||
return pr_nodes |
|
||||
|
|
||||
|
|
||||
class ContributorsResults(BaseModel): |
|
||||
contributors: Counter |
|
||||
commenters: Counter |
|
||||
reviewers: Counter |
|
||||
translation_reviewers: Counter |
|
||||
authors: Dict[str, Author] |
|
||||
|
|
||||
|
|
||||
def get_contributors(pr_nodes: List[PullRequestNode]) -> ContributorsResults: |
|
||||
contributors = Counter() |
|
||||
commenters = Counter() |
|
||||
reviewers = Counter() |
|
||||
translation_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: |
|
||||
commenters[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 label in pr.labels.nodes: |
|
||||
if label.name == "lang-all": |
|
||||
translation_reviewers[review.author.login] += 1 |
|
||||
break |
|
||||
for reviewer in pr_reviewers: |
|
||||
reviewers[reviewer] += 1 |
|
||||
if pr.state == "MERGED" and pr.author: |
|
||||
contributors[pr.author.login] += 1 |
|
||||
return ContributorsResults( |
|
||||
contributors=contributors, |
|
||||
commenters=commenters, |
|
||||
reviewers=reviewers, |
|
||||
translation_reviewers=translation_reviewers, |
|
||||
authors=authors, |
|
||||
) |
|
||||
|
|
||||
|
|
||||
def get_individual_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) |
|
||||
|
|
||||
tiers: DefaultDict[float, Dict[str, SponsorEntity]] = defaultdict(dict) |
|
||||
for node in nodes: |
|
||||
tiers[node.tier.monthlyPriceInDollars][node.sponsorEntity.login] = ( |
|
||||
node.sponsorEntity |
|
||||
) |
|
||||
return tiers |
|
||||
|
|
||||
|
|
||||
def get_top_users( |
|
||||
*, |
|
||||
counter: Counter, |
|
||||
authors: Dict[str, Author], |
|
||||
skip_users: Container[str], |
|
||||
min_count: int = 2, |
|
||||
): |
|
||||
users = [] |
|
||||
for commenter, count in counter.most_common(50): |
|
||||
if commenter in skip_users: |
|
||||
continue |
|
||||
if count >= min_count: |
|
||||
author = authors[commenter] |
|
||||
users.append( |
|
||||
{ |
|
||||
"login": commenter, |
|
||||
"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.model_dump_json()}") |
|
||||
g = Github(settings.input_token.get_secret_value()) |
|
||||
repo = g.get_repo(settings.github_repository) |
|
||||
discussion_nodes = get_discussion_nodes(settings=settings) |
|
||||
experts_results = get_discussions_experts(discussion_nodes=discussion_nodes) |
|
||||
pr_nodes = get_pr_nodes(settings=settings) |
|
||||
contributors_results = get_contributors(pr_nodes=pr_nodes) |
|
||||
authors = {**experts_results.authors, **contributors_results.authors} |
|
||||
maintainers_logins = {"tiangolo"} |
|
||||
bot_names = {"codecov", "github-actions", "pre-commit-ci", "dependabot"} |
|
||||
maintainers = [] |
|
||||
for login in maintainers_logins: |
|
||||
user = authors[login] |
|
||||
maintainers.append( |
|
||||
{ |
|
||||
"login": login, |
|
||||
"answers": experts_results.commenters[login], |
|
||||
"prs": contributors_results.contributors[login], |
|
||||
"avatarUrl": user.avatarUrl, |
|
||||
"url": user.url, |
|
||||
} |
|
||||
) |
|
||||
|
|
||||
skip_users = maintainers_logins | bot_names |
|
||||
experts = get_top_users( |
|
||||
counter=experts_results.commenters, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
last_month_experts = get_top_users( |
|
||||
counter=experts_results.last_month_commenters, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
three_months_experts = get_top_users( |
|
||||
counter=experts_results.three_months_commenters, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
six_months_experts = get_top_users( |
|
||||
counter=experts_results.six_months_commenters, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
one_year_experts = get_top_users( |
|
||||
counter=experts_results.one_year_commenters, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
top_contributors = get_top_users( |
|
||||
counter=contributors_results.contributors, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
top_reviewers = get_top_users( |
|
||||
counter=contributors_results.reviewers, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
top_translations_reviewers = get_top_users( |
|
||||
counter=contributors_results.translation_reviewers, |
|
||||
authors=authors, |
|
||||
skip_users=skip_users, |
|
||||
) |
|
||||
|
|
||||
tiers = get_individual_sponsors(settings=settings) |
|
||||
keys = list(tiers.keys()) |
|
||||
keys.sort(reverse=True) |
|
||||
sponsors = [] |
|
||||
for key in keys: |
|
||||
sponsor_group = [] |
|
||||
for login, sponsor in tiers[key].items(): |
|
||||
sponsor_group.append( |
|
||||
{"login": login, "avatarUrl": sponsor.avatarUrl, "url": sponsor.url} |
|
||||
) |
|
||||
sponsors.append(sponsor_group) |
|
||||
|
|
||||
people = { |
|
||||
"maintainers": maintainers, |
|
||||
"experts": experts, |
|
||||
"last_month_experts": last_month_experts, |
|
||||
"three_months_experts": three_months_experts, |
|
||||
"six_months_experts": six_months_experts, |
|
||||
"one_year_experts": one_year_experts, |
|
||||
"top_contributors": top_contributors, |
|
||||
"top_reviewers": top_reviewers, |
|
||||
"top_translations_reviewers": top_translations_reviewers, |
|
||||
} |
|
||||
github_sponsors = { |
|
||||
"sponsors": sponsors, |
|
||||
} |
|
||||
# For local development |
|
||||
# people_path = Path("../../../../docs/en/data/people.yml") |
|
||||
people_path = Path("./docs/en/data/people.yml") |
|
||||
github_sponsors_path = Path("./docs/en/data/github_sponsors.yml") |
|
||||
people_old_content = people_path.read_text(encoding="utf-8") |
|
||||
github_sponsors_old_content = github_sponsors_path.read_text(encoding="utf-8") |
|
||||
new_people_content = yaml.dump( |
|
||||
people, sort_keys=False, width=200, allow_unicode=True |
|
||||
) |
|
||||
new_github_sponsors_content = yaml.dump( |
|
||||
github_sponsors, sort_keys=False, width=200, allow_unicode=True |
|
||||
) |
|
||||
if ( |
|
||||
people_old_content == new_people_content |
|
||||
and github_sponsors_old_content == new_github_sponsors_content |
|
||||
): |
|
||||
logging.info("The FastAPI People data hasn't changed, finishing.") |
|
||||
sys.exit(0) |
|
||||
people_path.write_text(new_people_content, encoding="utf-8") |
|
||||
github_sponsors_path.write_text(new_github_sponsors_content, 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", "[email protected]"], 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), str(github_sponsors_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") |
|
@ -0,0 +1,53 @@ |
|||||
|
name: FastAPI People Contributors |
||||
|
|
||||
|
on: |
||||
|
schedule: |
||||
|
- cron: "0 3 1 * *" |
||||
|
workflow_dispatch: |
||||
|
inputs: |
||||
|
debug_enabled: |
||||
|
description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)" |
||||
|
required: false |
||||
|
default: "false" |
||||
|
|
||||
|
env: |
||||
|
UV_SYSTEM_PYTHON: 1 |
||||
|
|
||||
|
jobs: |
||||
|
job: |
||||
|
if: github.repository_owner == 'fastapi' |
||||
|
runs-on: ubuntu-latest |
||||
|
permissions: |
||||
|
contents: write |
||||
|
steps: |
||||
|
- name: Dump GitHub context |
||||
|
env: |
||||
|
GITHUB_CONTEXT: ${{ toJson(github) }} |
||||
|
run: echo "$GITHUB_CONTEXT" |
||||
|
- uses: actions/checkout@v4 |
||||
|
- name: Set up Python |
||||
|
uses: actions/setup-python@v5 |
||||
|
with: |
||||
|
python-version: "3.11" |
||||
|
- name: Setup uv |
||||
|
uses: astral-sh/setup-uv@v5 |
||||
|
with: |
||||
|
version: "0.4.15" |
||||
|
enable-cache: true |
||||
|
cache-dependency-glob: | |
||||
|
requirements**.txt |
||||
|
pyproject.toml |
||||
|
- name: Install Dependencies |
||||
|
run: uv pip install -r requirements-github-actions.txt |
||||
|
# Allow debugging with tmate |
||||
|
- name: Setup tmate session |
||||
|
uses: mxschmitt/action-tmate@v3 |
||||
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} |
||||
|
with: |
||||
|
limit-access-to-actor: true |
||||
|
env: |
||||
|
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} |
||||
|
- name: FastAPI People Contributors |
||||
|
run: python ./scripts/contributors.py |
||||
|
env: |
||||
|
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} |
@ -34,7 +34,7 @@ jobs: |
|||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} |
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} |
||||
with: |
with: |
||||
limit-access-to-actor: true |
limit-access-to-actor: true |
||||
- uses: tiangolo/[email protected].1 |
- uses: tiangolo/[email protected].2 |
||||
with: |
with: |
||||
token: ${{ secrets.GITHUB_TOKEN }} |
token: ${{ secrets.GITHUB_TOKEN }} |
||||
latest_changes_file: docs/en/docs/release-notes.md |
latest_changes_file: docs/en/docs/release-notes.md |
||||
|
@ -35,7 +35,7 @@ jobs: |
|||||
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} |
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} |
||||
run: python -m build |
run: python -m build |
||||
- name: Publish |
- name: Publish |
||||
uses: pypa/[email protected]0.1 |
uses: pypa/[email protected]2.4 |
||||
- name: Dump GitHub context |
- name: Dump GitHub context |
||||
env: |
env: |
||||
GITHUB_CONTEXT: ${{ toJson(github) }} |
GITHUB_CONTEXT: ${{ toJson(github) }} |
||||
|
@ -0,0 +1,52 @@ |
|||||
|
name: FastAPI People Sponsors |
||||
|
|
||||
|
on: |
||||
|
schedule: |
||||
|
- cron: "0 6 1 * *" |
||||
|
workflow_dispatch: |
||||
|
inputs: |
||||
|
debug_enabled: |
||||
|
description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)" |
||||
|
required: false |
||||
|
default: "false" |
||||
|
|
||||
|
env: |
||||
|
UV_SYSTEM_PYTHON: 1 |
||||
|
|
||||
|
jobs: |
||||
|
job: |
||||
|
if: github.repository_owner == 'fastapi' |
||||
|
runs-on: ubuntu-latest |
||||
|
permissions: |
||||
|
contents: write |
||||
|
steps: |
||||
|
- name: Dump GitHub context |
||||
|
env: |
||||
|
GITHUB_CONTEXT: ${{ toJson(github) }} |
||||
|
run: echo "$GITHUB_CONTEXT" |
||||
|
- uses: actions/checkout@v4 |
||||
|
- name: Set up Python |
||||
|
uses: actions/setup-python@v5 |
||||
|
with: |
||||
|
python-version: "3.11" |
||||
|
- name: Setup uv |
||||
|
uses: astral-sh/setup-uv@v5 |
||||
|
with: |
||||
|
version: "0.4.15" |
||||
|
enable-cache: true |
||||
|
cache-dependency-glob: | |
||||
|
requirements**.txt |
||||
|
pyproject.toml |
||||
|
- name: Install Dependencies |
||||
|
run: uv pip install -r requirements-github-actions.txt |
||||
|
# Allow debugging with tmate |
||||
|
- name: Setup tmate session |
||||
|
uses: mxschmitt/action-tmate@v3 |
||||
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} |
||||
|
with: |
||||
|
limit-access-to-actor: true |
||||
|
- name: FastAPI People Sponsors |
||||
|
run: python ./scripts/sponsors.py |
||||
|
env: |
||||
|
SPONSORS_TOKEN: ${{ secrets.SPONSORS_TOKEN }} |
||||
|
PR_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} |
@ -0,0 +1,40 @@ |
|||||
|
name: Update Topic Repos |
||||
|
|
||||
|
on: |
||||
|
schedule: |
||||
|
- cron: "0 12 1 * *" |
||||
|
workflow_dispatch: |
||||
|
|
||||
|
env: |
||||
|
UV_SYSTEM_PYTHON: 1 |
||||
|
|
||||
|
jobs: |
||||
|
topic-repos: |
||||
|
if: github.repository_owner == 'fastapi' |
||||
|
runs-on: ubuntu-latest |
||||
|
permissions: |
||||
|
contents: write |
||||
|
steps: |
||||
|
- name: Dump GitHub context |
||||
|
env: |
||||
|
GITHUB_CONTEXT: ${{ toJson(github) }} |
||||
|
run: echo "$GITHUB_CONTEXT" |
||||
|
- uses: actions/checkout@v4 |
||||
|
- name: Set up Python |
||||
|
uses: actions/setup-python@v5 |
||||
|
with: |
||||
|
python-version: "3.11" |
||||
|
- name: Setup uv |
||||
|
uses: astral-sh/setup-uv@v5 |
||||
|
with: |
||||
|
version: "0.4.15" |
||||
|
enable-cache: true |
||||
|
cache-dependency-glob: | |
||||
|
requirements**.txt |
||||
|
pyproject.toml |
||||
|
- name: Install GitHub Actions dependencies |
||||
|
run: uv pip install -r requirements-github-actions.txt |
||||
|
- name: Update Topic Repos |
||||
|
run: python ./scripts/topic_repos.py |
||||
|
env: |
||||
|
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} |
@ -8,7 +8,7 @@ Aus diesem Grund werden diese üblicherweise in Umgebungsvariablen bereitgestell |
|||||
|
|
||||
## Umgebungsvariablen |
## Umgebungsvariablen |
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Wenn Sie bereits wissen, was „Umgebungsvariablen“ sind und wie man sie verwendet, können Sie gerne mit dem nächsten Abschnitt weiter unten fortfahren. |
Wenn Sie bereits wissen, was „Umgebungsvariablen“ sind und wie man sie verwendet, können Sie gerne mit dem nächsten Abschnitt weiter unten fortfahren. |
||||
|
|
||||
@ -67,7 +67,7 @@ name = os.getenv("MY_NAME", "World") |
|||||
print(f"Hello {name} from Python") |
print(f"Hello {name} from Python") |
||||
``` |
``` |
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Das zweite Argument für <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> ist der zurückzugebende Defaultwert. |
Das zweite Argument für <a href="https://docs.python.org/3.8/library/os.html#os.getenv" class="external-link" target="_blank">`os.getenv()`</a> ist der zurückzugebende Defaultwert. |
||||
|
|
||||
@ -124,7 +124,7 @@ Hello World from Python |
|||||
|
|
||||
</div> |
</div> |
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Weitere Informationen dazu finden Sie unter <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>. |
Weitere Informationen dazu finden Sie unter <a href="https://12factor.net/config" class="external-link" target="_blank">The Twelve-Factor App: Config</a>. |
||||
|
|
||||
@ -180,9 +180,7 @@ Sie können dieselben Validierungs-Funktionen und -Tools verwenden, die Sie für |
|||||
|
|
||||
//// tab | Pydantic v2 |
//// tab | Pydantic v2 |
||||
|
|
||||
```Python hl_lines="2 5-8 11" |
{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} |
||||
{!> ../../../docs_src/settings/tutorial001.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
//// |
||||
|
|
||||
@ -194,13 +192,11 @@ In Pydantic v1 würden Sie `BaseSettings` direkt von `pydantic` statt von `pydan |
|||||
|
|
||||
/// |
/// |
||||
|
|
||||
```Python hl_lines="2 5-8 11" |
{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} |
||||
{!> ../../../docs_src/settings/tutorial001_pv1.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
//// |
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Für ein schnelles Copy-and-paste verwenden Sie nicht dieses Beispiel, sondern das letzte unten. |
Für ein schnelles Copy-and-paste verwenden Sie nicht dieses Beispiel, sondern das letzte unten. |
||||
|
|
||||
@ -214,9 +210,7 @@ Als Nächstes werden die Daten konvertiert und validiert. Wenn Sie also dieses ` |
|||||
|
|
||||
Dann können Sie das neue `settings`-Objekt in Ihrer Anwendung verwenden: |
Dann können Sie das neue `settings`-Objekt in Ihrer Anwendung verwenden: |
||||
|
|
||||
```Python hl_lines="18-20" |
{* ../../docs_src/settings/tutorial001.py hl[18:20] *} |
||||
{!../../../docs_src/settings/tutorial001.py!} |
|
||||
``` |
|
||||
|
|
||||
### Den Server ausführen |
### Den Server ausführen |
||||
|
|
||||
@ -232,7 +226,7 @@ $ ADMIN_EMAIL="[email protected]" APP_NAME="ChimichangApp" uvicorn main:app |
|||||
|
|
||||
</div> |
</div> |
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Um mehrere Umgebungsvariablen für einen einzelnen Befehl festzulegen, trennen Sie diese einfach durch ein Leerzeichen und fügen Sie alle vor dem Befehl ein. |
Um mehrere Umgebungsvariablen für einen einzelnen Befehl festzulegen, trennen Sie diese einfach durch ein Leerzeichen und fügen Sie alle vor dem Befehl ein. |
||||
|
|
||||
@ -250,17 +244,13 @@ Sie könnten diese Einstellungen in eine andere Moduldatei einfügen, wie Sie in |
|||||
|
|
||||
Sie könnten beispielsweise eine Datei `config.py` haben mit: |
Sie könnten beispielsweise eine Datei `config.py` haben mit: |
||||
|
|
||||
```Python |
{* ../../docs_src/settings/app01/config.py *} |
||||
{!../../../docs_src/settings/app01/config.py!} |
|
||||
``` |
|
||||
|
|
||||
Und dann verwenden Sie diese in einer Datei `main.py`: |
Und dann verwenden Sie diese in einer Datei `main.py`: |
||||
|
|
||||
```Python hl_lines="3 11-13" |
{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} |
||||
{!../../../docs_src/settings/app01/main.py!} |
|
||||
``` |
|
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Sie benötigen außerdem eine Datei `__init__.py`, wie in [Größere Anwendungen – mehrere Dateien](../tutorial/bigger-applications.md){.internal-link target=_blank} gesehen. |
Sie benötigen außerdem eine Datei `__init__.py`, wie in [Größere Anwendungen – mehrere Dateien](../tutorial/bigger-applications.md){.internal-link target=_blank} gesehen. |
||||
|
|
||||
@ -276,9 +266,7 @@ Dies könnte besonders beim Testen nützlich sein, da es sehr einfach ist, eine |
|||||
|
|
||||
Ausgehend vom vorherigen Beispiel könnte Ihre Datei `config.py` so aussehen: |
Ausgehend vom vorherigen Beispiel könnte Ihre Datei `config.py` so aussehen: |
||||
|
|
||||
```Python hl_lines="10" |
{* ../../docs_src/settings/app02/config.py hl[10] *} |
||||
{!../../../docs_src/settings/app02/config.py!} |
|
||||
``` |
|
||||
|
|
||||
Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erstellen. |
Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erstellen. |
||||
|
|
||||
@ -286,37 +274,9 @@ Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erste |
|||||
|
|
||||
Jetzt erstellen wir eine Abhängigkeit, die ein neues `config.Settings()` zurückgibt. |
Jetzt erstellen wir eine Abhängigkeit, die ein neues `config.Settings()` zurückgibt. |
||||
|
|
||||
//// tab | Python 3.9+ |
{* ../../docs_src/settings/app02_an_py39/main.py hl[6,12:13] *} |
||||
|
|
||||
```Python hl_lines="6 12-13" |
|
||||
{!> ../../../docs_src/settings/app02_an_py39/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Python 3.8+ |
|
||||
|
|
||||
```Python hl_lines="6 12-13" |
|
||||
{!> ../../../docs_src/settings/app02_an/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Python 3.8+ nicht annotiert |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Bevorzugen Sie die `Annotated`-Version, falls möglich. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
```Python hl_lines="5 11-12" |
|
||||
{!> ../../../docs_src/settings/app02/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Wir werden das `@lru_cache` in Kürze besprechen. |
Wir werden das `@lru_cache` in Kürze besprechen. |
||||
|
|
||||
@ -326,43 +286,13 @@ Im Moment nehmen Sie an, dass `get_settings()` eine normale Funktion ist. |
|||||
|
|
||||
Und dann können wir das von der *Pfadoperation-Funktion* als Abhängigkeit einfordern und es überall dort verwenden, wo wir es brauchen. |
Und dann können wir das von der *Pfadoperation-Funktion* als Abhängigkeit einfordern und es überall dort verwenden, wo wir es brauchen. |
||||
|
|
||||
//// tab | Python 3.9+ |
{* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} |
||||
|
|
||||
```Python hl_lines="17 19-21" |
|
||||
{!> ../../../docs_src/settings/app02_an_py39/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Python 3.8+ |
|
||||
|
|
||||
```Python hl_lines="17 19-21" |
|
||||
{!> ../../../docs_src/settings/app02_an/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Python 3.8+ nicht annotiert |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Bevorzugen Sie die `Annotated`-Version, falls möglich. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
```Python hl_lines="16 18-20" |
|
||||
{!> ../../../docs_src/settings/app02/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
### Einstellungen und Tests |
### Einstellungen und Tests |
||||
|
|
||||
Dann wäre es sehr einfach, beim Testen ein anderes Einstellungsobjekt bereitzustellen, indem man eine Abhängigkeitsüberschreibung für `get_settings` erstellt: |
Dann wäre es sehr einfach, beim Testen ein anderes Einstellungsobjekt bereitzustellen, indem man eine Abhängigkeitsüberschreibung für `get_settings` erstellt: |
||||
|
|
||||
```Python hl_lines="9-10 13 21" |
{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} |
||||
{!../../../docs_src/settings/app02/test_main.py!} |
|
||||
``` |
|
||||
|
|
||||
Bei der Abhängigkeitsüberschreibung legen wir einen neuen Wert für `admin_email` fest, wenn wir das neue `Settings`-Objekt erstellen, und geben dann dieses neue Objekt zurück. |
Bei der Abhängigkeitsüberschreibung legen wir einen neuen Wert für `admin_email` fest, wenn wir das neue `Settings`-Objekt erstellen, und geben dann dieses neue Objekt zurück. |
||||
|
|
||||
@ -374,7 +304,7 @@ Wenn Sie viele Einstellungen haben, die sich möglicherweise oft ändern, vielle |
|||||
|
|
||||
Diese Praxis ist so weit verbreitet, dass sie einen Namen hat. Diese Umgebungsvariablen werden üblicherweise in einer Datei `.env` abgelegt und die Datei wird „dotenv“ genannt. |
Diese Praxis ist so weit verbreitet, dass sie einen Namen hat. Diese Umgebungsvariablen werden üblicherweise in einer Datei `.env` abgelegt und die Datei wird „dotenv“ genannt. |
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Eine Datei, die mit einem Punkt (`.`) beginnt, ist eine versteckte Datei in Unix-ähnlichen Systemen wie Linux und macOS. |
Eine Datei, die mit einem Punkt (`.`) beginnt, ist eine versteckte Datei in Unix-ähnlichen Systemen wie Linux und macOS. |
||||
|
|
||||
@ -384,7 +314,7 @@ Aber eine dotenv-Datei muss nicht unbedingt genau diesen Dateinamen haben. |
|||||
|
|
||||
Pydantic unterstützt das Lesen dieser Dateitypen mithilfe einer externen Bibliothek. Weitere Informationen finden Sie unter <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>. |
Pydantic unterstützt das Lesen dieser Dateitypen mithilfe einer externen Bibliothek. Weitere Informationen finden Sie unter <a href="https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support" class="external-link" target="_blank">Pydantic Settings: Dotenv (.env) support</a>. |
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Damit das funktioniert, müssen Sie `pip install python-dotenv` ausführen. |
Damit das funktioniert, müssen Sie `pip install python-dotenv` ausführen. |
||||
|
|
||||
@ -405,11 +335,9 @@ Und dann aktualisieren Sie Ihre `config.py` mit: |
|||||
|
|
||||
//// tab | Pydantic v2 |
//// tab | Pydantic v2 |
||||
|
|
||||
```Python hl_lines="9" |
{* ../../docs_src/settings/app03_an/config.py hl[9] *} |
||||
{!> ../../../docs_src/settings/app03_an/config.py!} |
|
||||
``` |
|
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Configuration</a>. |
Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter <a href="https://docs.pydantic.dev/latest/concepts/config/" class="external-link" target="_blank">Pydantic: Configuration</a>. |
||||
|
|
||||
@ -419,11 +347,9 @@ Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet. |
|||||
|
|
||||
//// tab | Pydantic v1 |
//// tab | Pydantic v1 |
||||
|
|
||||
```Python hl_lines="9-10" |
{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} |
||||
{!> ../../../docs_src/settings/app03_an/config_pv1.py!} |
|
||||
``` |
|
||||
|
|
||||
/// tip | "Tipp" |
/// tip | Tipp |
||||
|
|
||||
Die Klasse `Config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. |
Die Klasse `Config` wird nur für die Pydantic-Konfiguration verwendet. Weitere Informationen finden Sie unter <a href="https://docs.pydantic.dev/1.10/usage/model_config/" class="external-link" target="_blank">Pydantic Model Config</a>. |
||||
|
|
||||
@ -462,35 +388,7 @@ würden wir dieses Objekt für jeden Request erstellen und die `.env`-Datei für |
|||||
|
|
||||
Da wir jedoch den `@lru_cache`-Dekorator oben verwenden, wird das `Settings`-Objekt nur einmal erstellt, nämlich beim ersten Aufruf. ✔️ |
Da wir jedoch den `@lru_cache`-Dekorator oben verwenden, wird das `Settings`-Objekt nur einmal erstellt, nämlich beim ersten Aufruf. ✔️ |
||||
|
|
||||
//// tab | Python 3.9+ |
{* ../../docs_src/settings/app03_an_py39/main.py hl[1,11] *} |
||||
|
|
||||
```Python hl_lines="1 11" |
|
||||
{!> ../../../docs_src/settings/app03_an_py39/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Python 3.8+ |
|
||||
|
|
||||
```Python hl_lines="1 11" |
|
||||
{!> ../../../docs_src/settings/app03_an/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Python 3.8+ nicht annotiert |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Bevorzugen Sie die `Annotated`-Version, falls möglich. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
```Python hl_lines="1 10" |
|
||||
{!> ../../../docs_src/settings/app03/main.py!} |
|
||||
``` |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
Dann wird bei allen nachfolgenden Aufrufen von `get_settings()`, in den Abhängigkeiten für darauffolgende Requests, dasselbe Objekt zurückgegeben, das beim ersten Aufruf zurückgegeben wurde, anstatt den Code von `get_settings()` erneut auszuführen und ein neues `Settings`-Objekt zu erstellen. |
Dann wird bei allen nachfolgenden Aufrufen von `get_settings()`, in den Abhängigkeiten für darauffolgende Requests, dasselbe Objekt zurückgegeben, das beim ersten Aufruf zurückgegeben wurde, anstatt den Code von `get_settings()` erneut auszuführen und ein neues `Settings`-Objekt zu erstellen. |
||||
|
|
||||
|
@ -1,484 +0,0 @@ |
|||||
# Entwicklung – Mitwirken |
|
||||
|
|
||||
Vielleicht möchten Sie sich zuerst die grundlegenden Möglichkeiten anschauen, [FastAPI zu helfen und Hilfe zu erhalten](help-fastapi.md){.internal-link target=_blank}. |
|
||||
|
|
||||
## Entwicklung |
|
||||
|
|
||||
Wenn Sie das <a href="https://github.com/fastapi/fastapi" class="external-link" target="_blank">fastapi Repository</a> bereits geklont haben und tief in den Code eintauchen möchten, hier einen Leitfaden zum Einrichten Ihrer Umgebung. |
|
||||
|
|
||||
### Virtuelle Umgebung mit `venv` |
|
||||
|
|
||||
Sie können mit dem Python-Modul `venv` in einem Verzeichnis eine isolierte virtuelle lokale Umgebung erstellen. Machen wir das im geklonten Repository (da wo sich die `requirements.txt` befindet): |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ python -m venv env |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
Das erstellt ein Verzeichnis `./env/` mit den Python-Binärdateien und Sie können dann Packages in dieser lokalen Umgebung installieren. |
|
||||
|
|
||||
### Umgebung aktivieren |
|
||||
|
|
||||
Aktivieren Sie die neue Umgebung mit: |
|
||||
|
|
||||
//// tab | Linux, macOS |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ source ./env/bin/activate |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Windows PowerShell |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ .\env\Scripts\Activate.ps1 |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Windows Bash |
|
||||
|
|
||||
Oder, wenn Sie Bash für Windows verwenden (z. B. <a href="https://gitforwindows.org/" class="external-link" target="_blank">Git Bash</a>): |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ source ./env/Scripts/activate |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
Um zu überprüfen, ob es funktioniert hat, geben Sie ein: |
|
||||
|
|
||||
//// tab | Linux, macOS, Windows Bash |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ which pip |
|
||||
|
|
||||
some/directory/fastapi/env/bin/pip |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
//// tab | Windows PowerShell |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ Get-Command pip |
|
||||
|
|
||||
some/directory/fastapi/env/bin/pip |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
//// |
|
||||
|
|
||||
Wenn die `pip` Binärdatei unter `env/bin/pip` angezeigt wird, hat es funktioniert. 🎉 |
|
||||
|
|
||||
Stellen Sie sicher, dass Sie über die neueste Version von pip in Ihrer lokalen Umgebung verfügen, um Fehler bei den nächsten Schritten zu vermeiden: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ python -m pip install --upgrade pip |
|
||||
|
|
||||
---> 100% |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Aktivieren Sie jedes Mal, wenn Sie ein neues Package mit `pip` in dieser Umgebung installieren, die Umgebung erneut. |
|
||||
|
|
||||
Dadurch wird sichergestellt, dass Sie, wenn Sie ein von diesem Package installiertes Terminalprogramm verwenden, das Programm aus Ihrer lokalen Umgebung verwenden und kein anderes, das global installiert sein könnte. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
### Benötigtes mit pip installieren |
|
||||
|
|
||||
Nachdem Sie die Umgebung wie oben beschrieben aktiviert haben: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ pip install -r requirements.txt |
|
||||
|
|
||||
---> 100% |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
Das installiert alle Abhängigkeiten und Ihr lokales FastAPI in Ihrer lokalen Umgebung. |
|
||||
|
|
||||
#### Das lokale FastAPI verwenden |
|
||||
|
|
||||
Wenn Sie eine Python-Datei erstellen, die FastAPI importiert und verwendet, und diese mit dem Python aus Ihrer lokalen Umgebung ausführen, wird Ihr geklonter lokaler FastAPI-Quellcode verwendet. |
|
||||
|
|
||||
Und wenn Sie diesen lokalen FastAPI-Quellcode aktualisieren und dann die Python-Datei erneut ausführen, wird die neue Version von FastAPI verwendet, die Sie gerade bearbeitet haben. |
|
||||
|
|
||||
Auf diese Weise müssen Sie Ihre lokale Version nicht „installieren“, um jede Änderung testen zu können. |
|
||||
|
|
||||
/// note | "Technische Details" |
|
||||
|
|
||||
Das geschieht nur, wenn Sie die Installation mit der enthaltenen `requirements.txt` durchführen, anstatt `pip install fastapi` direkt auszuführen. |
|
||||
|
|
||||
Das liegt daran, dass in der Datei `requirements.txt` die lokale Version von FastAPI mit der Option `-e` für die Installation im „editierbaren“ Modus markiert ist. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
### Den Code formatieren |
|
||||
|
|
||||
Es gibt ein Skript, das, wenn Sie es ausführen, Ihren gesamten Code formatiert und bereinigt: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ bash scripts/format.sh |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
Es sortiert auch alle Ihre Importe automatisch. |
|
||||
|
|
||||
Damit es sie richtig sortiert, muss FastAPI lokal in Ihrer Umgebung installiert sein, mit dem Befehl vom obigen Abschnitt, welcher `-e` verwendet. |
|
||||
|
|
||||
## Dokumentation |
|
||||
|
|
||||
Stellen Sie zunächst sicher, dass Sie Ihre Umgebung wie oben beschrieben einrichten, was alles Benötigte installiert. |
|
||||
|
|
||||
### Dokumentation live |
|
||||
|
|
||||
Während der lokalen Entwicklung gibt es ein Skript, das die Site erstellt, auf Änderungen prüft und direkt neu lädt (Live Reload): |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ python ./scripts/docs.py live |
|
||||
|
|
||||
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 |
|
||||
<span style="color: green;">[INFO]</span> Start watching changes |
|
||||
<span style="color: green;">[INFO]</span> Start detecting changes |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
Das stellt die Dokumentation unter `http://127.0.0.1:8008` bereit. |
|
||||
|
|
||||
Auf diese Weise können Sie die Dokumentation/Quelldateien bearbeiten und die Änderungen live sehen. |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Alternativ können Sie die Schritte des Skripts auch manuell ausführen. |
|
||||
|
|
||||
Gehen Sie in das Verzeichnis für die entsprechende Sprache. Das für die englischsprachige Hauptdokumentation befindet sich unter `docs/en/`: |
|
||||
|
|
||||
```console |
|
||||
$ cd docs/en/ |
|
||||
``` |
|
||||
|
|
||||
Führen Sie dann `mkdocs` in diesem Verzeichnis aus: |
|
||||
|
|
||||
```console |
|
||||
$ mkdocs serve --dev-addr 8008 |
|
||||
``` |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
#### Typer-CLI (optional) |
|
||||
|
|
||||
Die Anleitung hier zeigt Ihnen, wie Sie das Skript unter `./scripts/docs.py` direkt mit dem `python` Programm verwenden. |
|
||||
|
|
||||
Sie können aber auch <a href="https://typer.tiangolo.com/typer-cli/" class="external-link" target="_blank">Typer CLI</a> verwenden und erhalten dann Autovervollständigung für Kommandos in Ihrem Terminal, nach dem Sie dessen Vervollständigung installiert haben. |
|
||||
|
|
||||
Wenn Sie Typer CLI installieren, können Sie die Vervollständigung installieren mit: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ typer --install-completion |
|
||||
|
|
||||
zsh completion installed in /home/user/.bashrc. |
|
||||
Completion will take effect once you restart the terminal. |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
### Dokumentationsstruktur |
|
||||
|
|
||||
Die Dokumentation verwendet <a href="https://www.mkdocs.org/" class="external-link" target="_blank">MkDocs</a>. |
|
||||
|
|
||||
Und es gibt zusätzliche Tools/Skripte für Übersetzungen, in `./scripts/docs.py`. |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Sie müssen sich den Code in `./scripts/docs.py` nicht anschauen, verwenden Sie ihn einfach in der Kommandozeile. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
Die gesamte Dokumentation befindet sich im Markdown-Format im Verzeichnis `./docs/en/`. |
|
||||
|
|
||||
Viele der Tutorials enthalten Codeblöcke. |
|
||||
|
|
||||
In den meisten Fällen handelt es sich bei diesen Codeblöcken um vollständige Anwendungen, die unverändert ausgeführt werden können. |
|
||||
|
|
||||
Tatsächlich sind diese Codeblöcke nicht Teil des Markdowns, sondern Python-Dateien im Verzeichnis `./docs_src/`. |
|
||||
|
|
||||
Und diese Python-Dateien werden beim Generieren der Site in die Dokumentation eingefügt. |
|
||||
|
|
||||
### Dokumentation für Tests |
|
||||
|
|
||||
Tatsächlich arbeiten die meisten Tests mit den Beispielquelldateien in der Dokumentation. |
|
||||
|
|
||||
Dadurch wird sichergestellt, dass: |
|
||||
|
|
||||
* Die Dokumentation aktuell ist. |
|
||||
* Die Dokumentationsbeispiele ohne Änderung ausgeführt werden können. |
|
||||
* Die meisten Funktionalitäten durch die Dokumentation abgedeckt werden, sichergestellt durch die Testabdeckung. |
|
||||
|
|
||||
#### Gleichzeitig Apps und Dokumentation |
|
||||
|
|
||||
Wenn Sie die Beispiele ausführen, mit z. B.: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ uvicorn tutorial001:app --reload |
|
||||
|
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
wird das, da Uvicorn standardmäßig den Port `8000` verwendet, mit der Dokumentation auf dem Port `8008` nicht in Konflikt geraten. |
|
||||
|
|
||||
### Übersetzungen |
|
||||
|
|
||||
Hilfe bei Übersetzungen wird SEHR geschätzt! Und es kann nicht getan werden, ohne die Hilfe der Gemeinschaft. 🌎 🚀 |
|
||||
|
|
||||
Hier sind die Schritte, die Ihnen bei Übersetzungen helfen. |
|
||||
|
|
||||
#### Tipps und Richtlinien |
|
||||
|
|
||||
* Schauen Sie nach <a href="https://github.com/fastapi/fastapi/pulls" class="external-link" target="_blank">aktuellen Pull Requests</a> für Ihre Sprache. Sie können die Pull Requests nach dem Label für Ihre Sprache filtern. Für Spanisch lautet das Label beispielsweise <a href="https://github.com/fastapi/fastapi/pulls?q=is%3Aopen+sort%3Aupdated-desc+label%3Alang-es+label%3Aawaiting-review" class="external-link" target="_blank">`lang-es`</a>. |
|
||||
|
|
||||
* Sehen Sie diese Pull Requests durch (Review), schlagen Sie Änderungen vor, oder segnen Sie sie ab (Approval). Bei den Sprachen, die ich nicht spreche, warte ich, bis mehrere andere die Übersetzung durchgesehen haben, bevor ich den Pull Request merge. |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Sie können <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request" class="external-link" target="_blank">Kommentare mit Änderungsvorschlägen</a> zu vorhandenen Pull Requests hinzufügen. |
|
||||
|
|
||||
Schauen Sie sich die Dokumentation an, <a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-request-reviews" class="external-link" target="_blank">wie man ein Review zu einem Pull Request hinzufügt</a>, welches den PR absegnet oder Änderungen vorschlägt. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
* Überprüfen Sie, ob es eine <a href="https://github.com/fastapi/fastapi/discussions/categories/translations" class="external-link" target="_blank">GitHub-Diskussion</a> gibt, die Übersetzungen für Ihre Sprache koordiniert. Sie können sie abonnieren, und wenn ein neuer Pull Request zum Review vorliegt, wird der Diskussion automatisch ein Kommentar hinzugefügt. |
|
||||
|
|
||||
* Wenn Sie Seiten übersetzen, fügen Sie einen einzelnen Pull Request pro übersetzter Seite hinzu. Dadurch wird es für andere viel einfacher, ihn zu durchzusehen. |
|
||||
|
|
||||
* Um den Zwei-Buchstaben-Code für die Sprache zu finden, die Sie übersetzen möchten, schauen Sie sich die Tabelle <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes" class="external-link" target= verwenden "_blank">List of ISO 639-1 codes</a> an. |
|
||||
|
|
||||
#### Vorhandene Sprache |
|
||||
|
|
||||
Angenommen, Sie möchten eine Seite für eine Sprache übersetzen, die bereits Übersetzungen für einige Seiten hat, beispielsweise für Spanisch. |
|
||||
|
|
||||
Im Spanischen lautet der Zwei-Buchstaben-Code `es`. Das Verzeichnis für spanische Übersetzungen befindet sich also unter `docs/es/`. |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Die Haupt („offizielle“) Sprache ist Englisch und befindet sich unter `docs/en/`. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
Führen Sie nun den Live-Server für die Dokumentation auf Spanisch aus: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
// Verwenden Sie das Kommando „live“ und fügen Sie den Sprach-Code als Argument hinten an |
|
||||
$ python ./scripts/docs.py live es |
|
||||
|
|
||||
<span style="color: green;">[INFO]</span> Serving on http://127.0.0.1:8008 |
|
||||
<span style="color: green;">[INFO]</span> Start watching changes |
|
||||
<span style="color: green;">[INFO]</span> Start detecting changes |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Alternativ können Sie die Schritte des Skripts auch manuell ausführen. |
|
||||
|
|
||||
Gehen Sie in das Sprachverzeichnis, für die spanischen Übersetzungen ist das `docs/es/`: |
|
||||
|
|
||||
```console |
|
||||
$ cd docs/es/ |
|
||||
``` |
|
||||
|
|
||||
Dann führen Sie in dem Verzeichnis `mkdocs` aus: |
|
||||
|
|
||||
```console |
|
||||
$ mkdocs serve --dev-addr 8008 |
|
||||
``` |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
Jetzt können Sie auf <a href="http://127.0.0.1:8008" class="external-link" target="_blank">http://127.0.0.1:8008</a> gehen und Ihre Änderungen live sehen. |
|
||||
|
|
||||
Sie werden sehen, dass jede Sprache alle Seiten hat. Einige Seiten sind jedoch nicht übersetzt und haben oben eine Info-Box, dass die Übersetzung noch fehlt. |
|
||||
|
|
||||
Nehmen wir nun an, Sie möchten eine Übersetzung für den Abschnitt [Features](features.md){.internal-link target=_blank} hinzufügen. |
|
||||
|
|
||||
* Kopieren Sie die Datei: |
|
||||
|
|
||||
``` |
|
||||
docs/en/docs/features.md |
|
||||
``` |
|
||||
|
|
||||
* Fügen Sie sie an genau derselben Stelle ein, jedoch für die Sprache, die Sie übersetzen möchten, z. B.: |
|
||||
|
|
||||
``` |
|
||||
docs/es/docs/features.md |
|
||||
``` |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Beachten Sie, dass die einzige Änderung in Pfad und Dateiname der Sprachcode ist, von `en` zu `es`. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
Wenn Sie in Ihrem Browser nachsehen, werden Sie feststellen, dass die Dokumentation jetzt Ihren neuen Abschnitt anzeigt (die Info-Box oben ist verschwunden). 🎉 |
|
||||
|
|
||||
Jetzt können Sie alles übersetzen und beim Speichern sehen, wie es aussieht. |
|
||||
|
|
||||
#### Neue Sprache |
|
||||
|
|
||||
Nehmen wir an, Sie möchten Übersetzungen für eine Sprache hinzufügen, die noch nicht übersetzt ist, nicht einmal einige Seiten. |
|
||||
|
|
||||
Angenommen, Sie möchten Übersetzungen für Kreolisch hinzufügen, diese sind jedoch noch nicht in den Dokumenten enthalten. |
|
||||
|
|
||||
Wenn Sie den Link von oben überprüfen, lautet der Sprachcode für Kreolisch `ht`. |
|
||||
|
|
||||
Der nächste Schritt besteht darin, das Skript auszuführen, um ein neues Übersetzungsverzeichnis zu erstellen: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
// Verwenden Sie das Kommando new-lang und fügen Sie den Sprach-Code als Argument hinten an |
|
||||
$ python ./scripts/docs.py new-lang ht |
|
||||
|
|
||||
Successfully initialized: docs/ht |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
Jetzt können Sie in Ihrem Code-Editor das neu erstellte Verzeichnis `docs/ht/` sehen. |
|
||||
|
|
||||
Obiges Kommando hat eine Datei `docs/ht/mkdocs.yml` mit einer Minimal-Konfiguration erstellt, die alles von der `en`-Version erbt: |
|
||||
|
|
||||
```yaml |
|
||||
INHERIT: ../en/mkdocs.yml |
|
||||
``` |
|
||||
|
|
||||
/// tip | "Tipp" |
|
||||
|
|
||||
Sie können diese Datei mit diesem Inhalt auch einfach manuell erstellen. |
|
||||
|
|
||||
/// |
|
||||
|
|
||||
Das Kommando hat auch eine Dummy-Datei `docs/ht/index.md` für die Hauptseite erstellt. Sie können mit der Übersetzung dieser Datei beginnen. |
|
||||
|
|
||||
Sie können nun mit den obigen Instruktionen für eine „vorhandene Sprache“ fortfahren. |
|
||||
|
|
||||
Fügen Sie dem ersten Pull Request beide Dateien `docs/ht/mkdocs.yml` und `docs/ht/index.md` bei. 🎉 |
|
||||
|
|
||||
#### Vorschau des Ergebnisses |
|
||||
|
|
||||
Wie bereits oben erwähnt, können Sie `./scripts/docs.py` mit dem Befehl `live` verwenden, um eine Vorschau der Ergebnisse anzuzeigen (oder `mkdocs serve`). |
|
||||
|
|
||||
Sobald Sie fertig sind, können Sie auch alles so testen, wie es online aussehen würde, einschließlich aller anderen Sprachen. |
|
||||
|
|
||||
Bauen Sie dazu zunächst die gesamte Dokumentation: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
// Verwenden Sie das Kommando „build-all“, das wird ein wenig dauern |
|
||||
$ python ./scripts/docs.py build-all |
|
||||
|
|
||||
Building docs for: en |
|
||||
Building docs for: es |
|
||||
Successfully built docs for: es |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
Dadurch werden alle diese unabhängigen MkDocs-Sites für jede Sprache erstellt, kombiniert und das endgültige Resultat unter `./site/` gespeichert. |
|
||||
|
|
||||
Dieses können Sie dann mit dem Befehl `serve` bereitstellen: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
// Verwenden Sie das Kommando „serve“ nachdem Sie „build-all“ ausgeführt haben. |
|
||||
$ python ./scripts/docs.py serve |
|
||||
|
|
||||
Warning: this is a very simple server. For development, use mkdocs serve instead. |
|
||||
This is here only to preview a site with translations already built. |
|
||||
Make sure you run the build-all command first. |
|
||||
Serving at: http://127.0.0.1:8008 |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
#### Übersetzungsspezifische Tipps und Richtlinien |
|
||||
|
|
||||
* Übersetzen Sie nur die Markdown-Dokumente (`.md`). Übersetzen Sie nicht die Codebeispiele unter `./docs_src`. |
|
||||
|
|
||||
* In Codeblöcken innerhalb des Markdown-Dokuments, übersetzen Sie Kommentare (`# ein Kommentar`), aber lassen Sie den Rest unverändert. |
|
||||
|
|
||||
* Ändern Sie nichts, was in "``" (Inline-Code) eingeschlossen ist. |
|
||||
|
|
||||
* In Zeilen, die mit `===` oder `!!!` beginnen, übersetzen Sie nur den ` "... Text ..."`-Teil. Lassen Sie den Rest unverändert. |
|
||||
|
|
||||
* Sie können Info-Boxen wie `!!! warning` mit beispielsweise `!!! warning "Achtung"` übersetzen. Aber ändern Sie nicht das Wort direkt nach dem `!!!`, es bestimmt die Farbe der Info-Box. |
|
||||
|
|
||||
* Ändern Sie nicht die Pfade in Links zu Bildern, Codedateien, Markdown Dokumenten. |
|
||||
|
|
||||
* Wenn ein Markdown-Dokument übersetzt ist, ändern sich allerdings unter Umständen die `#hash-teile` in Links zu dessen Überschriften. Aktualisieren Sie diese Links, wenn möglich. |
|
||||
* Suchen Sie im übersetzten Dokument nach solchen Links mit dem Regex `#[^# ]`. |
|
||||
* Suchen Sie in allen bereits in ihre Sprache übersetzen Dokumenten nach `ihr-ubersetztes-dokument.md`. VS Code hat beispielsweise eine Option „Bearbeiten“ -> „In Dateien suchen“. |
|
||||
* Übersetzen Sie bei der Übersetzung eines Dokuments nicht „im Voraus“ `#hash-teile`, die zu Überschriften in noch nicht übersetzten Dokumenten verlinken. |
|
||||
|
|
||||
## Tests |
|
||||
|
|
||||
Es gibt ein Skript, das Sie lokal ausführen können, um den gesamten Code zu testen und Code Coverage Reporte in HTML zu generieren: |
|
||||
|
|
||||
<div class="termy"> |
|
||||
|
|
||||
```console |
|
||||
$ bash scripts/test-cov-html.sh |
|
||||
``` |
|
||||
|
|
||||
</div> |
|
||||
|
|
||||
Dieses Kommando generiert ein Verzeichnis `./htmlcov/`. Wenn Sie die Datei `./htmlcov/index.html` in Ihrem Browser öffnen, können Sie interaktiv die Codebereiche erkunden, die von den Tests abgedeckt werden, und feststellen, ob Bereiche fehlen. |
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue