diff --git a/aiprompts/ai_instruct/processing/heroscript.md b/aiprompts/ai_instruct/processing/heroscript.md new file mode 100644 index 00000000..61734af9 --- /dev/null +++ b/aiprompts/ai_instruct/processing/heroscript.md @@ -0,0 +1,109 @@ +## INTENT + +we use heroscript to communicate actions and events in a structured format. +we want you to parse user intents and generate the corresponding heroscript. + +ONLY RETURN THE HEROSCRIPT STATEMENTS, can be more than 1 + +## HEROSCRIPT FORMAT + +HeroScript is a concise scripting language with the following structure: + +```heroscript +!!actor.action_name + param1: 'value1' + param2: 'value with spaces' + multiline_description: ' + This is a multiline description. + It can span multiple lines. + ' + arg1 arg2 // Arguments without keys + +!!actor.action_name2 param1:something param2:'something with spaces' nr:3 +``` + +Key characteristics: + +- **Actions**: Start with `!!`, followed by `actor.action_name` (e.g., `!!mailclient.configure`). +- **Parameters**: Defined as `key:value`. Values can be quoted for spaces. +- **Multiline Support**: Parameters like `description` can span multiple lines. +- **Arguments**: Values without keys (e.g., `arg1`). +- params can be on 1 line, with spaces in between +- time can be as +1h, +1d, +1w (hour, day, week), ofcourse 1 can be any number, +1 means 1 hour from now +- time format is: dd/mm/yyyy hh:mm (ONLY USE THIS) +- comma separation is used a lot in arguments e.g. 'jan,kristof' or 'jan , kristof' remove spaces, is list of 2 +- note only !! is at start of line, rest has spaces per instruction +- make one empty line between 1 heroscript statements +- everything after // is comment + +## HEROSCRIPT SCHEMA + +the language we understand + +### calendar management + +```heroscript +!!calendar.create when:'+1h' descr:'this is event to discuss eng' attendees:'jan,kristof' name:'meet1' tags:'eng,urgent' +!!calendar.delete name:'meet1' +!!calendar.list tags:'urgent' + +``` + +### contact management + +```heroscript +!!contact.add name:'jan' email:'jan@example.com' phone:'123-456-7890' +!!contact.remove name:'jan' +!!contact.list + +``` + +### task management + +```heroscript +!!task.create title:'Prepare presentation' due:'+1d' assignee:'jan' name:'task1' tags:'eng,urgent' + deadline:'+10d' duration:'1h' +!!task.update name:'task1' status:'in progress' +!!task.delete name:'task1' +!!task.list + +``` + +### project management + +```heroscript +!!project.create title:'Cloud Product Development' description:'Track progress of cloud product development' name:'cloud_prod' +!!project.update name:'cloud_prod' status:'in progress' +!!project.delete name:'cloud_prod' +!!project.list +!!project.tasks_list name:'cloud_prod' //required properties are name, description, and assignee of not given ask +!!project.tasks_add names:'task1, task2' +!!project.tasks_remove names:'task1, task2' + +``` + +### SUPPORTED TAGS + +only tags supported are: + +- for intent: eng, prod, support, mgmt, marketing +- for urgency: urgent, high, medium, low + +### generic remarks + +- names are lowercase and snake_case, can be distilled out of title if only title given, often a user will say name but that means title +- time: format of returned data or time is always dd/mm/yyyy hh:min + +## IMPORTANT STARTING INFO + +- current time is 10/08/2025 05:10 , use this to define any time-related parameters + +## USER INTENT + +I want a meeting tomorrow 10am, where we will discuss our new product for the cloud with jan and alex, and the urgency is high + +also let me know which other meetings I have which are urgent + +can you make a project where we can track the progress of our new product development? Name is 'Cloud Product Development' + +Please add tasks to the project in line to creating specifications, design documents, and implementation plans. \ No newline at end of file diff --git a/aiprompts/ai_instruct/processing/heroscript2.md b/aiprompts/ai_instruct/processing/heroscript2.md new file mode 100644 index 00000000..b6db8fe9 --- /dev/null +++ b/aiprompts/ai_instruct/processing/heroscript2.md @@ -0,0 +1,64 @@ +SYSTEM +You are a HeroScript compiler. Convert user intents into valid HeroScript statements. + +OUTPUT RULES + +1) Return ONLY HeroScript statements. No prose, no backticks. +2) Separate each statement with exactly ONE blank line. +3) Keys use snake_case. Names are lowercase snake_case derived from titles (non-alnum → "_", collapse repeats, trim). +4) Lists are comma-separated with NO spaces (e.g., "jan,alex"). +5) Times: OUTPUT MUST BE ABSOLUTE in "dd/mm/yyyy hh:mm" (Europe/Zurich). Convert relative times (e.g., "tomorrow 10am") using CURRENT_TIME. +6) Tags: include at most one intent tag and at most one urgency tag when present. + - intent: eng,prod,support,mgmt,marketing + - urgency: urgent,high,medium,low +7) Quotes: quote values containing spaces; otherwise omit quotes (allowed either way). +8) Comments only with // if the user explicitly asks for explanations; otherwise omit. + +SCHEMA (exact actions & parameters) + +!!calendar.create when:'dd/mm/yyyy hh:mm' name:'' descr:'' attendees:'a,b,c' tags:'intent,urgency' +!!calendar.delete name:'' +!!calendar.list [tags:'tag1,tag2'] + +!!contact.add name:'' email:'' phone:'' +!!contact.remove name:'' +!!contact.list + +!!task.create title:'' name:'<name>' [due:'dd/mm/yyyy hh:mm'] [assignee:'<name>'] [tags:'intent,urgency'] [deadline:'dd/mm/yyyy hh:mm'] [duration:'<Nd Nh Nm> or <Nh>'] +!!task.update name:'<name>' [status:'in progress|done|blocked|todo'] +!!task.delete name:'<name>' +!!task.list + +!!project.create title:'<title>' description:'<text>' name:'<name>' +!!project.update name:'<name>' [status:'in progress|done|blocked|todo'] +!!project.delete name:'<name>' +!!project.list +!!project.tasks_list name:'<project_name>' +!!project.tasks_add name:'<project_name>' names:'task_a,task_b' +!!project.tasks_remove name:'<project_name>' names:'task_a,task_b' + +NORMALIZATION & INFERENCE (silent) +- Derive names from titles when missing (see rule 3). Ensure consistency across statements. +- Map phrases to tags when obvious (e.g., "new product" ⇒ intent: prod; "high priority" ⇒ urgency: high). +- Attendees: split on commas, trim, lowercase given names. +- If the user asks for “urgent meetings,” use tags:'urgent' specifically. +- Prefer concise descriptions pulled from the user’s phrasing. +- Name's are required, if missing ask for clarification. +- For calendar management, ensure to include all relevant details such as time, attendees, and description. + + +CURRENT_TIME + +10/08/2025 05:10 + +USER_MESSAGE + +I want a meeting tomorrow 10am, where we will discuss our new product for the cloud with jan and alex, and the urgency is high + +also let me know which other meetings I have which are urgent + +can you make a project where we can track the progress of our new product development? Name is 'Cloud Product Development' + +Please add tasks to the project in line to creating specifications, design documents, and implementation plans. + +END diff --git a/aiprompts/ai_instruct/processing/intent.md b/aiprompts/ai_instruct/processing/intent.md new file mode 100644 index 00000000..9a2780ba --- /dev/null +++ b/aiprompts/ai_instruct/processing/intent.md @@ -0,0 +1,82 @@ +## INSTRUCTIONS + +the user will send me multiple instructions what they wants to do, I want you to put them in separate categories + +The categories we have defined are: + +- calendar management + - schedule meetings, events, reminders + - list these events + - delete them +- contact management + - add/remove contact information e.g. phone numbers, email addresses, address information + - list contacts, search +- task or project management + - anything we need to do, anything we need to track and plan + - create/update tasks, set deadlines + - mark tasks as complete + - delete tasks + - project management +- communication (chat, email) + - see what needs to be communicate e.g. send a chat to ... +- search statements + - find on internet, find specific information from my friends + +I want you to detect the intent and make multiple blocks out of the intent, each block should correspond to one of the identified intents, identify the intent with name of the category eg. calendar, only use above names + + + +what user wants to do, stay as close as possible to the original instructions, copy the exact instructions as where given by the user, we only need to sort the instructions in these blocks + +for each instruction make a separate block, e.g. if 2 tasks are given, create 2 blocks + +the format to return is: (note newline after each title of block) + +```template +===CALENDAR===\n + +$the copied text from what user wants + +===CONTACT===\n +... + +===QUESTION===\n + +put here what our system needs to ask to the user anything which is not clear + +===END===\n + +``` + +I want you to execute above on instructions as given by user below, give text back ONLY supporting the template + +note for format is only ===$NAME=== and then on next lines the original instructions from the user, don't change + +## special processing of info + +- if a date or time specified e.g. tomorrow, time, ... calculate back from current date + +## IMPORTANT STARTING INFO + +- current time is 10/08/2025 05:10 (format of returned data is always dd/mm/yyyy hh:min) + - use the current time to define formatted time out of instructions + - only return the formatted time + +## UNCLEAR INFO + +check in instructions e.g. things specified like you, me, ... +are not clear ask specifically who do you mean + +if task, specify per task, who needs to do it and when, make sure each instruction (block) is complete and clear for further processing + +be very specific with the questions e.g. who is you, ... + +## EXECUTE ABOVE ON THE FOLLOWING + +I am planning a birthday for my daughters tomorrow, there will be 10 people. + +I would like to know if you can help me with the preparations. + +I need a place for my daughter's birthday party. + +I need to send message to my wife isabelle that she needs to pick up the cake. \ No newline at end of file diff --git a/aiprompts/ai_instruct/uppy/fastapi.md b/aiprompts/ai_instruct/uppy/fastapi.md new file mode 100644 index 00000000..7aba132c --- /dev/null +++ b/aiprompts/ai_instruct/uppy/fastapi.md @@ -0,0 +1,16344 @@ +======================== +CODE SNIPPETS +======================== +TITLE: Basic HTTP Basic Auth with FastAPI +DESCRIPTION: This snippet demonstrates the basic implementation of HTTP Basic Authentication in FastAPI. It imports HTTPBasic and HTTPBasicCredentials, creates a security scheme, and uses it as a dependency in a path operation to retrieve the username and password. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/advanced/security/http-basic-auth.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, FastAPI, HTTPException, status +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + + +def get_current_username(credentials: HTTPBasicCredentials = Depends(security)): + return credentials.username + + +@app.get("/items/") +def read_items(username: str = Depends(get_current_username)): + return {"username": username} +``` + +---------------------------------------- + +TITLE: Creating a Basic FastAPI Application +DESCRIPTION: This Python code defines a simple FastAPI application with two endpoints: `/` which returns a greeting, and `/items/{item_id}` which returns the item ID and an optional query parameter. It demonstrates the basic structure of a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/he/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Implement Simple HTTP Basic Auth in FastAPI +DESCRIPTION: Demonstrates how to set up basic HTTP authentication in a FastAPI application using `HTTPBasic` and `HTTPBasicCredentials` to protect a path operation. It shows how to define a security dependency and access the provided username and password. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/security/http-basic-auth.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Depends +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + +@app.get("/users/me") +def read_current_user(credentials: HTTPBasicCredentials = Depends(security)): + return {"username": credentials.username, "password": credentials.password} +``` + +---------------------------------------- + +TITLE: Simple Function Example +DESCRIPTION: A basic Python function that takes a first name and last name, capitalizes them, and returns the full name. It demonstrates a simple string manipulation task. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +def get_full_name(first_name, last_name): + full_name = first_name.title() + " " + last_name.title() + return full_name + +print(get_full_name("john", "doe")) +``` + +---------------------------------------- + +TITLE: Creating a basic FastAPI application +DESCRIPTION: This Python code creates a basic FastAPI application with two endpoints: a root endpoint ("/") that returns a greeting and an endpoint for retrieving items by ID ("/items/{item_id}"). It uses type hints and Pydantic for data validation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI App +DESCRIPTION: Create a basic FastAPI application with two endpoints: `/` which returns a simple greeting, and `/items/{item_id}` which returns the item ID and an optional query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Creating a Basic FastAPI App +DESCRIPTION: This code snippet demonstrates how to create a basic FastAPI application with two GET routes: one for the root path ('/') and another for '/items/{item_id}'. The '/items/{item_id}' route accepts an integer item_id and an optional string query parameter q. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/index.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI and Defining Basic Routes +DESCRIPTION: This code initializes a FastAPI application and defines two GET routes: one for the root path ('/') and another for '/items/{item_id}' with a path parameter 'item_id' and an optional query parameter 'q'. It demonstrates basic route definition and parameter handling in FastAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/tr/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Create Basic API with FastAPI +DESCRIPTION: Create a simple FastAPI application with two endpoints: a root endpoint that returns a greeting and an `/items/{item_id}` endpoint that returns the item ID and an optional query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/index.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Initialize a Basic FastAPI Application +DESCRIPTION: This snippet demonstrates the standard setup for a FastAPI application, including importing `FastAPI` and defining a simple path operation. This forms the base for extending OpenAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/how-to/extending-openapi.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/") +async def read_items(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Basic FastAPI Application Testing with TestClient +DESCRIPTION: Demonstrates how to set up a basic FastAPI application and test it using `TestClient` from `fastapi.testclient` and `pytest`. This self-contained example shows a simple GET endpoint and its corresponding test function, illustrating the fundamental approach to testing FastAPI apps. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/testing.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + +@app.get("/") +def read_main(): + return {"msg": "Hello World"} + +client = TestClient(app) + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Create a Basic FastAPI Application +DESCRIPTION: This snippet shows the minimal Python code required to set up a FastAPI application. It imports `FastAPI`, creates an application instance, and defines a root endpoint (`/`) that returns a simple JSON message. This forms the foundation for any FastAPI project. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +async def read_root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Example JSON response for basic path parameter +DESCRIPTION: Shows the JSON output when accessing a FastAPI endpoint with a basic path parameter. This illustrates how the parameter's value is captured from the URL and returned in the response body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params.md#_snippet_1 + +LANGUAGE: JSON +CODE: +``` +{"item_id":"foo"} +``` + +---------------------------------------- + +TITLE: Creating a basic FastAPI application +DESCRIPTION: This Python code defines a simple FastAPI application with two endpoints: `/` which returns a greeting, and `/items/{item_id}` which returns the item ID and an optional query parameter. It imports FastAPI, creates an app instance, and defines the endpoints using decorators. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Returning a basic Response object +DESCRIPTION: This code snippet demonstrates how to return a basic Response object directly. It imports the Response class and returns an instance of it with custom content and media type. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/advanced/custom-response.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from fastapi.responses import Response + +app = FastAPI() + + +@app.get("/") +async def main(): + content = """ + <html> + <head> + <title>Some HTML in here + + +

Hello World!

+ + + """ + return Response(content=content, media_type="text/html") +``` + +---------------------------------------- + +TITLE: Adding Basic Type Hints to Function Parameters +DESCRIPTION: This snippet demonstrates how to add basic type hints (e.g., `str`) to function parameters. By specifying parameter types, developers gain improved editor autocompletion and static analysis capabilities, enhancing code reliability. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +{!../../docs_src/python_types/tutorial002.py!} +``` + +---------------------------------------- + +TITLE: Create a Basic FastAPI Application +DESCRIPTION: This Python code defines a minimal FastAPI application. It initializes an `app` instance and includes two basic GET endpoints: a root endpoint ('/') returning a simple JSON message, and an item endpoint ('/items/{item_id}') demonstrating path parameters and optional query parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/deployment/docker.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Creating a Basic FastAPI Application +DESCRIPTION: This code defines a basic FastAPI application with two routes: a root route ('/') that returns a simple JSON response, and an '/items/{item_id}' route that accepts an integer path parameter 'item_id' and an optional string query parameter 'q'. It uses the FastAPI framework to handle HTTP requests and responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fa/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Example JSON Response from FastAPI Root Endpoint +DESCRIPTION: This JSON snippet illustrates the typical response received when accessing the root endpoint (`/`) of the basic FastAPI application. It's a simple dictionary containing a 'message' key with 'Hello World' as its value, demonstrating a fundamental API output. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: JSON +CODE: +``` +{"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Creating a Basic FastAPI Application +DESCRIPTION: This code creates a basic FastAPI application with two endpoints: a root endpoint that returns a simple JSON response and an items endpoint that accepts an item ID and an optional query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/bn/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI App with Basic Endpoints +DESCRIPTION: Creates a FastAPI application instance and defines two GET endpoints: one for the root path ('/') and another for '/items/{item_id}' with a path parameter and an optional query parameter. It uses the FastAPI library and returns JSON responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Create a Basic FastAPI 'Hello World' Application +DESCRIPTION: This comprehensive snippet demonstrates the fundamental structure of a FastAPI application. It includes importing FastAPI, initializing the app, defining a GET route for the root path ('/'), and returning a simple JSON response. This is the typical starting point for any FastAPI project. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/first-steps.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +def read_root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: FastAPI Default Hello World Response +DESCRIPTION: This JSON snippet shows the typical 'Hello World' response returned by a basic FastAPI application when accessed via a web browser. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: JSON +CODE: +``` +{"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Pydantic Model Definition +DESCRIPTION: Defines a Pydantic model with type annotations for data validation and conversion. This example shows a basic Pydantic model with string and integer fields. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_18 + +LANGUAGE: Python +CODE: +``` +from typing import List + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tags: List[str] = [] + + +class Image(BaseModel): + url: str + name: str | None = None + + +class Offer(BaseModel): + name: str + description: str | None = None + price: float + items: List[Item] + +``` + +---------------------------------------- + +TITLE: Defining a Basic FastAPI App +DESCRIPTION: This code defines a simple FastAPI application with a single endpoint that returns a JSON response. It imports FastAPI, creates an app instance, and defines a GET route at the root path ('/'). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + +``` + +---------------------------------------- + +TITLE: Example FastAPI JSON response +DESCRIPTION: This JSON object represents the default response from a basic FastAPI application when accessed at its root URL, typically indicating a successful 'Hello World' message. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: json +CODE: +``` +{"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Secure Username Verification with HTTP Basic Auth +DESCRIPTION: This snippet shows how to securely verify a username and password using Python's secrets module to prevent timing attacks. It converts the username and password to UTF-8 encoded bytes before using secrets.compare_digest() to compare them. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/advanced/security/http-basic-auth.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +import secrets + +from fastapi import Depends, FastAPI, HTTPException, status +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + + +def get_current_username(credentials: HTTPBasicCredentials = Depends(security)): + correct_username = secrets.compare_digest(credentials.username, "stanleyjobson".encode("utf8")) + correct_password = secrets.compare_digest(credentials.password, "swordfish".encode("utf8")) + if not (correct_username and correct_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + return credentials.username + + +@app.get("/items/") +def read_items(username: str = Depends(get_current_username)): + return {"username": username} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI App with async +DESCRIPTION: Create a basic FastAPI application with two endpoints: `/` which returns a simple greeting, and `/items/{item_id}` which returns the item ID and an optional query parameter. This example uses `async def` for asynchronous execution. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/index.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: FastAPI: Basic Optional Query Parameter +DESCRIPTION: Demonstrates a basic FastAPI application with an optional query parameter `q` of type `Optional[str]`. This parameter is not required as its default value is `None`, allowing it to be omitted from the request. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: Optional[str] = None): + return {"q": q} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI Application +DESCRIPTION: This code initializes a basic FastAPI application with two GET endpoints: one for the root path ('/') and another for retrieving items by ID ('/items/{item_id}'). It demonstrates how to define path parameters and optional query parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Declaring Standard Python Built-in Types +DESCRIPTION: This section provides examples of declaring common built-in Python types using type hints. It covers basic types such as `int`, `float`, `bool`, and `bytes`, showcasing their straightforward application in function signatures. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +{!../../docs_src/python_types/tutorial005.py!} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI and Defining Basic Routes +DESCRIPTION: This code initializes a FastAPI application and defines two GET routes: one for the root path ('/') and another for '/items/{item_id}'. The '/items/{item_id}' route accepts an integer item_id and an optional string query parameter q. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/az/docs/index.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Simple FastAPI Application +DESCRIPTION: This is a minimal FastAPI application that defines a single route ('/') which returns a JSON response. It demonstrates the basic structure of a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + +``` + +---------------------------------------- + +TITLE: Creating a Basic FastAPI App with Async +DESCRIPTION: This code snippet demonstrates how to create a basic FastAPI application with asynchronous route handlers using `async def`. It includes two GET routes: one for the root path ('/') and another for '/items/{item_id}'. The '/items/{item_id}' route accepts an integer item_id and an optional string query parameter q. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/index.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Simple FastAPI Application +DESCRIPTION: This code defines a basic FastAPI application with a single endpoint that returns a JSON response. It imports the FastAPI class, creates an instance of it, and defines a path operation for the root path ('/'). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Create Basic FastAPI GET Endpoints +DESCRIPTION: This Python code defines a simple FastAPI application with two GET endpoints. The root endpoint ('/') returns a 'Hello World' message, and the '/items/{item_id}' endpoint retrieves an item by ID, optionally accepting a query parameter 'q'. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/index.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Basic FastAPI Application +DESCRIPTION: This code snippet defines a simple FastAPI application with a single endpoint that returns a JSON response. It imports the FastAPI class, creates an instance of it, and defines a path operation function that returns a dictionary. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"message": "Hello World"} + +``` + +---------------------------------------- + +TITLE: Create a basic Dockerfile for FastAPI +DESCRIPTION: This Dockerfile demonstrates how to build a basic Docker image for a FastAPI application using the official `tiangolo/uvicorn-gunicorn-fastapi:python3.9` base image. It copies `requirements.txt`, installs dependencies, and then copies the application code into the `/app` directory. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/deployment/docker.md#_snippet_4 + +LANGUAGE: Dockerfile +CODE: +``` +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app +``` + +---------------------------------------- + +TITLE: Define Basic FastAPI Application Endpoints +DESCRIPTION: Example Python code for a simple FastAPI application defining a root endpoint and an item endpoint with path and query parameters. Includes both synchronous and asynchronous function definitions for handling requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/index.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Securely Compare Credentials with secrets.compare_digest() in FastAPI +DESCRIPTION: Provides a complete example of implementing HTTP Basic Auth with secure credential validation in FastAPI. It uses Python's `secrets.compare_digest()` to prevent timing attacks by ensuring constant-time comparison of usernames and passwords, and raises an `HTTPException` with a 401 status code and `WWW-Authenticate` header on incorrect credentials. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/security/http-basic-auth.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +import secrets +from fastapi import FastAPI, Depends, HTTPException, status +from fastapi.security import HTTPBasic, HTTPBasicCredentials + +app = FastAPI() + +security = HTTPBasic() + +@app.get("/users/me") +def read_current_user(credentials: HTTPBasicCredentials = Depends(security)): + correct_username = secrets.compare_digest(credentials.username.encode("utf-8"), b"stanleyjobson") + correct_password = secrets.compare_digest(credentials.password.encode("utf-8"), b"swordfish") + if not (correct_username and correct_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + return {"username": credentials.username, "password": credentials.password} +``` + +---------------------------------------- + +TITLE: Basic FastAPI Test with TestClient +DESCRIPTION: This snippet demonstrates a basic test case for a FastAPI application using TestClient. It imports TestClient, creates an instance with the FastAPI app, sends a request, and asserts the response status code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/testing.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} + + +client = TestClient(app) + + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Simple FastAPI Application +DESCRIPTION: This code defines a basic FastAPI application with a single endpoint that returns a JSON response. It uses the FastAPI framework to create the API and the uvicorn server to run it. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Simple FastAPI Application +DESCRIPTION: This code defines a basic FastAPI application with a single endpoint that returns a JSON response. It imports the FastAPI class, creates an instance of it, and defines a path operation decorator for the root endpoint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + +``` + +---------------------------------------- + +TITLE: Basic Dockerfile for FastAPI +DESCRIPTION: This Dockerfile sets up a basic environment for running a FastAPI application using the tiangolo/uvicorn-gunicorn-fastapi base image. It copies the requirements file, installs dependencies, and then copies the application code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/deployment/docker.md#_snippet_18 + +LANGUAGE: Dockerfile +CODE: +``` +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app +``` + +---------------------------------------- + +TITLE: Creating a Basic FastAPI App +DESCRIPTION: This Python code defines a simple FastAPI application with two routes: a root route ('/') that returns a greeting and an '/items/{item_id}' route that returns an item ID and an optional query parameter. It imports FastAPI and uses decorators to define the routes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Define a Pydantic Model with a Basic List Field +DESCRIPTION: Demonstrates how to define a Pydantic model field as a simple Python `list`. This allows the field to accept a list of items without explicitly specifying their internal type, providing flexibility for data structures where item types are not strictly enforced. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-nested-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel + +class Item(BaseModel): + name: str + tags: list +``` + +---------------------------------------- + +TITLE: Simple FastAPI Application +DESCRIPTION: This code defines a basic FastAPI application with a single endpoint that returns a JSON response. It imports the FastAPI class, creates an instance of it, and defines a path operation decorator to handle requests to the root path. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Build Basic FastAPI Docker Image with Uvicorn and Gunicorn +DESCRIPTION: This Dockerfile provides a basic setup for a FastAPI application using the `tiangolo/uvicorn-gunicorn-fastapi` base image. It copies `requirements.txt`, installs dependencies, and then copies the application code. This is suitable for standard FastAPI projects. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/deployment/docker.md#_snippet_15 + +LANGUAGE: Dockerfile +CODE: +``` +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 + +COPY ./requirements.txt /app/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./app /app +``` + +---------------------------------------- + +TITLE: Body with Examples +DESCRIPTION: Demonstrates how to pass a single example for the expected data in `Body()`. This example shows how to define a request body with an example for the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/schema-extra-example.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import Body, FastAPI + +app = FastAPI() + + +@app.post("/items/") +async def create_item( + item: str = Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 50.2, + "tax": 3.2, + } + ] + ), +): + return item +``` + +---------------------------------------- + +TITLE: Create a basic FastAPI application with GET endpoints +DESCRIPTION: This code demonstrates how to define a simple FastAPI application with two GET endpoints: a root path (/) and an item path (/items/{item_id}). The item path includes a path parameter (item_id) and an optional query parameter (q). It shows both synchronous (def) and asynchronous (async def) implementations for the route handlers. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Create a basic FastAPI application with GET endpoints +DESCRIPTION: This code demonstrates how to define a simple FastAPI application with two GET endpoints: a root path (/) and an item path (/items/{item_id}). The item path includes a path parameter (item_id) and an optional query parameter (q). It shows both synchronous (def) and asynchronous (async def) implementations for the route handlers. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/README.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Simple FastAPI Application +DESCRIPTION: This code defines a basic FastAPI application with a single endpoint that returns a JSON response. It imports FastAPI, creates an app instance, and defines a path operation decorator for the root endpoint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + +``` + +---------------------------------------- + +TITLE: Basic FastAPI test with TestClient +DESCRIPTION: Demonstrates how to import TestClient, create an instance with your FastAPI app, and write a simple test function using pytest. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/testing.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} + + +client = TestClient(app) + + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Define a Basic List Field in Pydantic Model (Python 3.10+) +DESCRIPTION: This snippet demonstrates how to declare a field as a generic Python `list` within a Pydantic model using Python 3.10+ syntax. This indicates a list without specifying the type of its elements, allowing for flexible content. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +tags: list +``` + +---------------------------------------- + +TITLE: Define a simple query parameter dependency +DESCRIPTION: This dependency function extracts an optional query parameter `q` as a string. It serves as a basic example to illustrate sub-dependency concepts in FastAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/sub-dependencies.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +async def query_extractor(q: Optional[str] = None): + return q +``` + +---------------------------------------- + +TITLE: Define Basic Path Parameter in FastAPI +DESCRIPTION: Demonstrates how to define a simple path parameter in a FastAPI application. The parameter's value will be passed directly as a string without automatic type conversion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/path-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_item(item_id): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Simple Function Example +DESCRIPTION: This example demonstrates a simple function that concatenates a first name and last name, converting each to title case. It highlights the lack of autocompletion without type hints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +def get_full_name(first_name, last_name): + full_name = first_name.title() + " " + last_name.title() + return full_name + +print(get_full_name("john", "doe")) +``` + +---------------------------------------- + +TITLE: Create a Basic FastAPI Application with Models +DESCRIPTION: This Python code defines a simple FastAPI application. It includes Pydantic models (`Item` and `ResponseMessage`) for defining request and response data structures, and two basic path operations: a GET endpoint for the root and a POST endpoint for creating items. These models are crucial for FastAPI to generate OpenAPI schemas. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/generate-clients.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +class ResponseMessage(BaseModel): + message: str + + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.post("/items/{item_id}", response_model=ResponseMessage) +def create_item(item_id: int, item: Item): + return {"message": f"Item {item_id} created with name {item.name}"} +``` + +---------------------------------------- + +TITLE: Example CSS for static files +DESCRIPTION: A simple CSS snippet (`styles.css`) that would be served as a static file by FastAPI, demonstrating basic styling. This file is typically referenced from Jinja2 templates using `url_for`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/advanced/templates.md#_snippet_5 + +LANGUAGE: CSS +CODE: +``` +body { + font-family: sans-serif; + color: #333; +} +``` + +---------------------------------------- + +TITLE: Simple FastAPI Application +DESCRIPTION: This code defines a basic FastAPI application with a single endpoint that returns a JSON response. It uses the FastAPI framework to create an API endpoint at the root path ('/') that returns a JSON object with a 'message' key. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + +``` + +---------------------------------------- + +TITLE: Import FastAPI status module +DESCRIPTION: Demonstrates the basic import statement for the `status` module from `fastapi`, which provides access to HTTP status code constants. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/status.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import status +``` + +---------------------------------------- + +TITLE: Python Standard Context Manager Example +DESCRIPTION: Demonstrates the basic syntax and usage of a standard Python context manager with the 'with' statement. This pattern ensures that resources, such as files, are properly managed and closed automatically after their use, even if errors occur. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/events.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +with open("file.txt") as file: + file.read() +``` + +---------------------------------------- + +TITLE: Detailed Timing Attack Scenario Illustration +DESCRIPTION: Illustrates how a timing attack works by showing how Python's string comparison behavior can inadvertently leak information about correct characters in a username or password, allowing attackers to guess credentials more efficiently. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/security/http-basic-auth.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +if "johndoe" == "stanleyjobson" and "love123" == "swordfish": + ... +if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": + ... +``` + +---------------------------------------- + +TITLE: Illustrative Vulnerable Credential Comparison +DESCRIPTION: An example of a common, but insecure, way to compare credentials. This type of comparison is vulnerable to timing attacks because it may short-circuit, revealing information about the correctness of partial inputs based on response time. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/security/http-basic-auth.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): + # Return some error + ... +``` + +---------------------------------------- + +TITLE: Pydantic Model Example +DESCRIPTION: This example demonstrates a basic Pydantic model definition. It shows how to define a class with typed attributes, which Pydantic uses for data validation and conversion. This is a fundamental concept in FastAPI for handling request and response data. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_10 + +LANGUAGE: python +CODE: +``` +from typing import Union + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +# Input data +item_data = {"name": "Foo", "description": "", "price": 50.2, "tax": 3.6} + +item = Item(**item_data) + +print(item.name) +print(item.price + item.tax) + +item_data = {"name": "Foo", "price": 50.2} + +item = Item(**item_data) + +print(item.description) +``` + +---------------------------------------- + +TITLE: Define a Python Function Without Type Hints +DESCRIPTION: This example demonstrates a basic Python function that concatenates first and last names. Without type hints, code editors may not provide useful autocompletion for string methods, making development less efficient. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/python-types.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +def get_full_name(first_name, last_name): + return f"{first_name.title()} {last_name.title()}" + +# Example usage: +# print(get_full_name("john", "doe")) +``` + +---------------------------------------- + +TITLE: Initializing WebSocket endpoint +DESCRIPTION: Creates a WebSocket endpoint in a FastAPI application to handle incoming and outgoing messages. It defines the basic structure for handling WebSocket connections, receiving messages, and sending responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/advanced/websockets.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, WebSocket + +app = FastAPI() + + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text(f"Message text was: {data}") +``` + +---------------------------------------- + +TITLE: Simple Type Declarations +DESCRIPTION: This example demonstrates how to declare simple types such as int, float, bool, and bytes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +age: int +price: float +awake: bool +binary: bytes +``` + +---------------------------------------- + +TITLE: Body with Multiple Examples +DESCRIPTION: Demonstrates how to pass multiple examples for the expected data in `Body()`. This example shows how to define a request body with multiple examples for the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/schema-extra-example.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import Body, FastAPI + +app = FastAPI() + + +@app.post("/items/") +async def create_item( + item: str = Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 50.2, + "tax": 3.2, + }, + { + "name": "Bar", + "price": 62, + "description": "The Bar fighters", + "tax": 2.2, + }, + ] + ), +): + return item +``` + +---------------------------------------- + +TITLE: OpenAPI JSON Schema Example +DESCRIPTION: This JSON schema represents a basic OpenAPI definition generated by FastAPI, including the OpenAPI version, API title, version, and a simple path definition for '/items/'. It demonstrates the structure of the automatically generated API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: JSON +CODE: +``` +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +---------------------------------------- + +TITLE: Basic FastAPI Application Code +DESCRIPTION: A minimal FastAPI application demonstrating two API endpoints: a root endpoint ('/') returning a simple JSON message, and an item endpoint ('/items/{item_id}') that accepts an integer path parameter and an optional string query parameter. This serves as a foundational example for building RESTful APIs with FastAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/deployment/docker.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Creating a basic FastAPI application +DESCRIPTION: This Python code defines a simple FastAPI application with two endpoints: a root endpoint that returns a greeting and an /items/{item_id} endpoint that returns the item ID and an optional query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/deployment/docker.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Optional[str] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Run FastAPI Application with WebSockets +DESCRIPTION: Executes the FastAPI application in development mode using `fastapi dev`, making the basic WebSocket endpoint accessible for testing and interaction. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/websockets.md#_snippet_1 + +LANGUAGE: console +CODE: +``` +$ fastapi dev main.py +``` + +---------------------------------------- + +TITLE: Declare Simple Python Built-in Types +DESCRIPTION: This example demonstrates how to declare common built-in Python types such as `str`, `int`, `float`, `bool`, and `bytes` as type hints for function parameters. These simple type declarations provide clear expectations for function inputs and enhance code readability and maintainability. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/python-types.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +def process_data(name: str, count: int, price: float, is_active: bool, raw_data: bytes): + # Example function using various simple types + # Type hints ensure that arguments passed to this function conform to the specified types. + pass +``` + +---------------------------------------- + +TITLE: Define and Instantiate a Python Class +DESCRIPTION: Illustrates how to define a basic Python class with an `__init__` method and subsequently create an instance of that class, demonstrating that classes are callables and can be used in contexts requiring callable objects. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +---------------------------------------- + +TITLE: Declaring an Integer Type +DESCRIPTION: This snippet demonstrates how to declare an integer type for a parameter in FastAPI. It shows the basic syntax for type hinting in Python, which FastAPI uses for data validation and conversion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/index.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +item_id: int +``` + +---------------------------------------- + +TITLE: Handling WebSocket messages +DESCRIPTION: Demonstrates how to receive and send messages through a WebSocket connection in FastAPI. It shows the basic structure for handling WebSocket connections, receiving messages, and sending responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/advanced/websockets.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +await websocket.accept() +while True: + data = await websocket.receive_text() + await websocket.send_text(f"Message text was: {data}") +``` + +---------------------------------------- + +TITLE: Example JSON Request Body Structures +DESCRIPTION: Provides examples of valid JSON request bodies that conform to the `Item` Pydantic model. The first example includes all defined fields, while the second demonstrates how optional fields can be omitted while still being a valid request. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body.md#_snippet_2 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "An optional description", + "price": 45.2, + "tax": 3.5 +} + +{ + "name": "Foo", + "price": 45.2 +} +``` + +---------------------------------------- + +TITLE: Install Hypercorn +DESCRIPTION: Install Hypercorn, an ASGI server that supports HTTP/2. This command performs a basic installation without specific extras. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/deployment/manually.md#_snippet_1 + +LANGUAGE: Shell +CODE: +``` +pip install hypercorn +``` + +---------------------------------------- + +TITLE: Example JSON Request Body (All Fields) +DESCRIPTION: Illustrates a complete JSON object that fully conforms to the `Item` Pydantic model. This example includes all defined fields, both mandatory and optional, with representative values, demonstrating a typical request body structure. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body.md#_snippet_2 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "Uma descrição opcional", + "price": 45.2, + "tax": 3.5 +} +``` + +---------------------------------------- + +TITLE: Defining a Base Hero Model +DESCRIPTION: This snippet defines a base model for Hero data, containing common fields like name and age. This base model can be inherited by other models to avoid duplication. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +class HeroBase(SQLModel): + name: str = Field(index=True) + age: Optional[int] = Field(default=None, index=True) +``` + +---------------------------------------- + +TITLE: Run Uvicorn with basic host and port +DESCRIPTION: Defines the default command to run the Uvicorn server inside the Docker container, binding it to all network interfaces on port 80. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/deployment/docker.md#_snippet_4 + +LANGUAGE: Dockerfile +CODE: +``` +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +---------------------------------------- + +TITLE: FastAPI Dependency Injection Flow Diagram +DESCRIPTION: This Mermaid diagram visually represents a basic dependency injection flow in FastAPI. It shows how a single dependency function (`common_parameters`) can be reused and injected into multiple distinct path operations (`/items/` and `/users/`), illustrating the concept of shared dependencies. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/dependencies/index.md#_snippet_3 + +LANGUAGE: mermaid +CODE: +``` +graph TB + +common_parameters(["common_parameters"]) +read_items["/items/"] +read_users["/users/"] + +common_parameters --> read_items +common_parameters --> read_users +``` + +---------------------------------------- + +TITLE: Initializing a Dictionary - Python 3.8+ +DESCRIPTION: This snippet initializes a dictionary `prices` where keys are strings and values are floats. It uses the `Dict` type from the `typing` module to specify the key and value types. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +from typing import Dict + +prices: Dict[str, float] = {"apple": 1.5, "banana": 0.7} +``` + +---------------------------------------- + +TITLE: Database Dependency with yield +DESCRIPTION: Creates a database session, yields it for use in the route function, and then closes the session after the response is sent. Demonstrates the basic structure of a dependency using `yield` for cleanup. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +db = DBSession() +try: + yield db +finally: + db.close() +``` + +---------------------------------------- + +TITLE: Creating an async FastAPI application +DESCRIPTION: This Python code demonstrates how to create a basic FastAPI application using `async def` for asynchronous request handling. It includes two endpoints: a root endpoint and an endpoint for retrieving items by ID, both defined as asynchronous functions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/index.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Basic FastAPI Test with TestClient +DESCRIPTION: This snippet demonstrates how to use TestClient to test a FastAPI application. It imports TestClient, creates an instance with the FastAPI app, and then makes requests to the application, asserting the expected responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/testing.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} + + +client = TestClient(app) + + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Define a simple FastAPI application +DESCRIPTION: This Python code defines a basic FastAPI application with a single asynchronous GET endpoint at the root path. It returns a JSON object with a 'Hello World' message, serving as the application under test for the asynchronous examples. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/async-tests.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +async def read_root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Basic Jinja2 template for item display +DESCRIPTION: A simple Jinja2 template (`item.html`) demonstrating how to display a variable (`id`) passed from the FastAPI context. This template would typically reside in a 'templates' directory and be rendered by `Jinja2Templates`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/advanced/templates.md#_snippet_2 + +LANGUAGE: jinja +CODE: +``` +Item ID: {{ id }} +``` + +---------------------------------------- + +TITLE: Define Base SQLModel for Hero Attributes +DESCRIPTION: Creates `HeroBase`, a non-table SQLModel (which acts as a Pydantic model) that defines common attributes like `name` and `age`. This base class is used for inheritance to avoid duplicating fields across different representations of the Hero model. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/sql-databases.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from sqlmodel import Field, SQLModel + +class HeroBase(SQLModel): + name: str + age: Optional[int] = None +``` + +---------------------------------------- + +TITLE: Creating an Asynchronous FastAPI Application +DESCRIPTION: This Python code defines a simple FastAPI application with two asynchronous endpoints: `/` which returns a greeting, and `/items/{item_id}` which returns the item ID and an optional query parameter. It demonstrates the basic structure of a FastAPI application using async def. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/he/docs/index.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Initializing a Dictionary - Python 3.9+ +DESCRIPTION: This snippet initializes a dictionary `prices` where keys are strings and values are floats. It uses the built-in `dict` type with type parameters to specify the key and value types. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_10 + +LANGUAGE: Python +CODE: +``` +prices: dict[str, float] = {"apple": 1.5, "banana": 0.7} +``` + +---------------------------------------- + +TITLE: Mix Required, Default, and Optional Query Parameters in FastAPI +DESCRIPTION: Demonstrates the flexibility of FastAPI in handling a combination of required, default, and optional query parameters within a single endpoint. This example defines `needy` as required, `skip` with a default value, and `limit` as optional, showcasing robust and versatile parameter management for complex API designs. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Union +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_user_item( + item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None +): + item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} + return item +``` + +---------------------------------------- + +TITLE: Example PUT Request Body +DESCRIPTION: An example of a JSON request body sent with an HTTP PUT request. This body demonstrates how providing only a subset of fields can lead to default values being applied to missing fields during a full replacement operation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-updates.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +{ + "name": "Barz", + "price": 3, + "description": None, +} +``` + +---------------------------------------- + +TITLE: Overview of HTTP Methods and FastAPI Path Operation Decorators +DESCRIPTION: This documentation outlines the standard HTTP methods (GET, POST, PUT, DELETE) and their typical use cases in RESTful API design. It also lists the corresponding FastAPI decorators (`@app.get()`, `@app.post()`, etc.) used to associate functions with specific HTTP methods and paths. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: APIDOC +CODE: +``` +HTTP Methods: +- `POST`: Create data. +- `GET`: Read data. +- `PUT`: Update data. +- `DELETE`: Delete data. +- Less common methods: `OPTIONS`, `HEAD`, `PATCH`, `TRACE`. + +FastAPI Path Operation Decorators: +- `@app.post()` +- `@app.get()` +- `@app.put()` +- `@app.delete()` +- `@app.options()` +- `@app.head()` +- `@app.patch()` +- `@app.trace()` +``` + +---------------------------------------- + +TITLE: Define Dictionary-Returning Dependency in FastAPI +DESCRIPTION: Demonstrates a basic FastAPI dependency function that returns a dictionary of common query parameters. This function can then be injected into path operations, providing a structured way to manage common inputs. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} +``` + +---------------------------------------- + +TITLE: Import Pydantic BaseModel for Request Body Definition +DESCRIPTION: Imports the `BaseModel` class from the Pydantic library, which serves as the foundational class for defining structured data models. These models are essential for FastAPI to parse, validate, and serialize incoming request body data. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel +``` + +---------------------------------------- + +TITLE: Define Path Operation with APIRouter +DESCRIPTION: Demonstrates a basic GET path operation defined on an `APIRouter` instance. The path specified here is relative to any prefix configured on the router, allowing for clean and modular route definitions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/bigger-applications.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +---------------------------------------- + +TITLE: Displaying context variable in Jinja2 template +DESCRIPTION: Illustrates the basic syntax for embedding and displaying a variable (e.g., 'id') passed from the context dictionary within a Jinja2 HTML template using double curly braces. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/templates.md#_snippet_1 + +LANGUAGE: Jinja +CODE: +``` +Item ID: {{ id }} +``` + +---------------------------------------- + +TITLE: Define a Path Operation with APIRouter +DESCRIPTION: This snippet demonstrates a basic path operation definition using an APIRouter instance. It shows how to define a GET endpoint with a path parameter, illustrating the syntax for a route within a router. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/bigger-applications.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +@router.get("/{item_id}") +async def read_item(item_id: str): + ... +``` + +---------------------------------------- + +TITLE: FastAPI OpenAPI Specification Structure +DESCRIPTION: This JSON snippet illustrates the basic structure of the OpenAPI specification automatically generated by FastAPI. It includes metadata like API version and title, and defines the available paths and their HTTP methods, along with expected responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: JSON +CODE: +``` +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... + +``` + +---------------------------------------- + +TITLE: FastAPI Path Operation with Raw Token Dependency +DESCRIPTION: Illustrates a basic FastAPI path operation that directly receives a raw token string from an `OAuth2PasswordBearer` dependency, before processing it into a user object. This is typically the first step in a token-based authentication flow. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/get-current-user.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends +from fastapi.security import OAuth2PasswordBearer + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +async def read_users_me(token: str = Depends(oauth2_scheme)): + # This function would typically process the raw token + pass +``` + +---------------------------------------- + +TITLE: Basic FastAPI Application Testing with TestClient +DESCRIPTION: This snippet illustrates a fundamental approach to testing a FastAPI application. It demonstrates how to import and instantiate `TestClient` with your FastAPI app, then write a `pytest`-compatible function to send requests and assert the expected responses, all within a single file for simplicity. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/testing.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from fastapi.testclient import TestClient + +app = FastAPI() + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} + +client = TestClient(app) + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Awaiting and Sending Messages in WebSocket Route +DESCRIPTION: This snippet shows how to await messages from a WebSocket connection and send messages back to the client. It demonstrates the basic structure for handling WebSocket communication within a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/advanced/websockets.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +await websocket.accept() +while True: + data = await websocket.receive_text() + await websocket.send_text(f"Message text was: {data}") +``` + +---------------------------------------- + +TITLE: Adicionar tarefa em segundo plano com BackgroundTasks +DESCRIPTION: Este snippet demonstra como adicionar uma tarefa em segundo plano usando o método .add_task() do objeto BackgroundTasks. Ele recebe a função de tarefa e seus argumentos como parâmetros. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/background-tasks.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import BackgroundTasks, FastAPI + +app = FastAPI() + + +async def write_notification(email: str, message=""): + with open("log.txt", mode="w") as f: + f.write(f"notification for {email}: {message}") + + +@app.post("/send-notification/{email}") +async def send_notification(email: str, background_tasks: BackgroundTasks): + background_tasks.add_task(write_notification, email, message="some notification") + return {"message": "Notification sent in the background"} +``` + +---------------------------------------- + +TITLE: FastAPI Parameter Functions for Example Data +DESCRIPTION: Documents the parameters available in FastAPI's dependency injection functions (`Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, `File`) for declaring example data in OpenAPI documentation. Explains the difference and usage of JSON Schema `examples` and OpenAPI-specific `openapi_examples`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/schema-extra-example.md#_snippet_4 + +LANGUAGE: APIDOC +CODE: +``` +FastAPI Parameter Functions: Path(), Query(), Header(), Cookie(), Body(), Form(), File() + +These functions accept parameters to include example data in the generated OpenAPI (and JSON Schema) documentation. + +1. JSON Schema `examples` parameter: + - Purpose: To declare an array of examples that will be added to the JSON Schema for the parameter. + - Usage: `param: Type = Function(examples=[{... +``` + +---------------------------------------- + +TITLE: Implementing Basic StreamingResponse in FastAPI +DESCRIPTION: Shows how to use `StreamingResponse` to stream response bodies using an asynchronous generator. This is useful for sending large amounts of data incrementally without loading it all into memory at once. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/advanced/custom-response.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from fastapi.responses import StreamingResponse +import asyncio + +app = FastAPI() + +async def generate_data(): + for i in range(5): + yield f"data: {i}\n" + await asyncio.sleep(0.5) + +@app.get("/stream") +async def stream_example(): + return StreamingResponse(generate_data(), media_type="text/event-stream") +``` + +---------------------------------------- + +TITLE: FastAPI Generated OpenAPI JSON Schema Example +DESCRIPTION: An example of the OpenAPI JSON schema automatically generated by FastAPI, illustrating the basic structure including the OpenAPI version, API information, and defined paths with their operations and responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: JSON +CODE: +``` +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... + +``` + +---------------------------------------- + +TITLE: Python Standard Synchronous Context Manager +DESCRIPTION: Illustrates the basic usage of a synchronous context manager in Python using the `with` statement. This pattern ensures that resources, such as files, are properly acquired before use and released afterwards, even if errors occur. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/events.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +with open("file.txt") as file: + file.read() +``` + +---------------------------------------- + +TITLE: Initializing a Tuple and Set - Python 3.8+ +DESCRIPTION: This snippet initializes a tuple `items_t` with specific types for each element (int, int, str) and a set `items_s` where each element is of type bytes. It uses the `Tuple` and `Set` types from the `typing` module. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from typing import Tuple, Set + +items_t: Tuple[int, int, str] = (1, 2, "foo") +items_s: Set[bytes] = {b"hallo", b"welt"} +``` + +---------------------------------------- + +TITLE: Correct Type Mismatch with Type Conversion +DESCRIPTION: Building on the previous example, this snippet shows the corrected implementation where the integer `age` is explicitly converted to a string using `str(age)` before being included in the f-string. This resolves the type error and ensures the function operates correctly according to its type hints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/python-types.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +def get_name_and_age(name: str, age: int): + return f"Hello, {name}. You are {str(age)} years old." +``` + +---------------------------------------- + +TITLE: FastAPI Asynchronous GET Endpoints +DESCRIPTION: This Python example demonstrates the same FastAPI GET endpoints as the basic setup, but implements them using `async def`. This approach is recommended when your endpoint logic involves asynchronous operations (e.g., `await` calls) to prevent blocking the event loop. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/index.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Python Dictionary Unpacking for Merging Dictionaries +DESCRIPTION: A basic Python example showing how to merge two dictionaries using the `**` (double-asterisk) operator for dictionary unpacking. This technique is useful for combining predefined configurations with custom additions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/advanced/additional-responses.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +old_dict = { + "old key": "old value", + "second old key": "second old value" +} +new_dict = {**old_dict, "new key": "new value"} +``` + +---------------------------------------- + +TITLE: Function with Type Hints and Type Conversion +DESCRIPTION: This example shows how to fix a type error by converting the integer age to a string using str(age). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +def get_name_with_age(name: str, age: int): + name_with_age = name + " is this old: " + str(age) + return name_with_age +``` + +---------------------------------------- + +TITLE: Simple Types Declaration +DESCRIPTION: Demonstrates declaring variables with simple types such as int, float, bool and bytes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +age: int +price: float +awake: bool +file: bytes +``` + +---------------------------------------- + +TITLE: Define FastAPI Application Entrypoint in Dockerfile +DESCRIPTION: This snippet demonstrates the basic `CMD` instruction for a Dockerfile to run a FastAPI application using Uvicorn. It specifies the main application file and the port, serving as the container's default command when it starts. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/deployment/docker.md#_snippet_5 + +LANGUAGE: Dockerfile +CODE: +``` +CMD ["fastapi", "run", "app/main.py", "--port", "80"] +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: Demonstrates how to install FastAPI including its recommended 'standard' set of optional dependencies, which provide common functionalities like email validation, testing, templating, form parsing, and a production-ready server. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_13 + +LANGUAGE: Python +CODE: +``` +pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Python Function Parameters with Optional Types +DESCRIPTION: Illustrates the difference between a parameter with an `Optional` type hint and an actual optional parameter (one with a default value). Shows that a parameter typed `Optional[T]` without a default value is still required, leading to a `TypeError` if not provided, while accepting `None` as a valid value. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/python-types.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +say_hi() # Oh, no, this throws an error! 😱 +``` + +LANGUAGE: Python +CODE: +``` +say_hi(name=None) # This works, None is valid 🎉 +``` + +---------------------------------------- + +TITLE: Declare basic path parameters in FastAPI +DESCRIPTION: Demonstrates how to define a path parameter in a FastAPI path operation using Python's f-string like syntax. The value from the URL path is automatically passed as an argument to the decorated function, allowing for dynamic routing. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_item(item_id): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Defining a List Field with Type Hints +DESCRIPTION: Demonstrates how to define a list field with type hints for the elements within the list. The `tags` attribute is explicitly defined as a list of strings. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-nested-models.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list[str] = [] +``` + +---------------------------------------- + +TITLE: Import BackgroundTasks from FastAPI +DESCRIPTION: This code snippet demonstrates the standard way to import the `BackgroundTasks` class directly from the `fastapi` library. This class is essential for defining and managing background tasks within your FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/background.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import BackgroundTasks +``` + +---------------------------------------- + +TITLE: Field Examples in Pydantic Models +DESCRIPTION: Declares examples for fields within a Pydantic model using the `Field` function. This allows providing example values for each field in the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/schema-extra-example.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel, Field + + +class Item(BaseModel): + name: str = Field(examples=["Foo"]) + description: Optional[str] = Field(default=None, examples=["A very nice Item"]) + price: float = Field(examples=[50.2]) + tax: Optional[float] = Field(default=None, examples=[3.2]) +``` + +---------------------------------------- + +TITLE: Define a Simple Test Path Operation in FastAPI +DESCRIPTION: This snippet provides a basic FastAPI path operation (`/hello`) that returns a JSON message. It serves as a simple endpoint to verify that the FastAPI application is running correctly and accessible after configuring custom documentation assets. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/how-to/custom-docs-ui-assets.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.get("/hello") +async def hello(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Implement OAuth2 Password Bearer Scheme in FastAPI +DESCRIPTION: This example demonstrates a basic FastAPI application using `OAuth2PasswordBearer` to secure an endpoint. It shows how to initialize the scheme with a `tokenUrl` and use it as a dependency to extract the authentication token from incoming requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/security/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, FastAPI +from fastapi.security import OAuth2PasswordBearer + +app = FastAPI() + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +@app.get("/users/me") +async def read_users_me(token: str = Depends(oauth2_scheme)): + return {"token": token} +``` + +---------------------------------------- + +TITLE: Criar uma função de tarefa para BackgroundTasks +DESCRIPTION: Este snippet mostra como criar uma função para ser executada como uma tarefa em segundo plano. A função grava em um arquivo, simulando o envio de um e-mail. A função pode ser async def ou def. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/background-tasks.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +def write_notification(email: str, message=""): + with open("log.txt", mode="w") as f: + f.write(f"notification for {email}: {message}") +``` + +---------------------------------------- + +TITLE: Creating a Data Model with Pydantic +DESCRIPTION: This code snippet shows how to define a data model using Pydantic's `BaseModel`. The model defines the structure and types of the expected data, including optional fields with default values. This enables automatic data validation and serialization. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None +``` + +---------------------------------------- + +TITLE: Import Pydantic BaseModel +DESCRIPTION: Imports the `BaseModel` class from the Pydantic library, which is the foundational class for defining data models used in FastAPI for request body validation and serialization. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel +``` + +---------------------------------------- + +TITLE: Initialize New Language Directory for FastAPI Documentation +DESCRIPTION: This command utilizes the `docs.py` script to create a new directory structure for a new language, such as Latin (`la`). It sets up the necessary files, including a basic `mkdocs.yml` and `index.md`, to begin translations for a previously unsupported language. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/contributing.md#_snippet_8 + +LANGUAGE: Shell +CODE: +``` +python ./scripts/docs.py new-lang la +``` + +---------------------------------------- + +TITLE: Returning JSON Response in FastAPI +DESCRIPTION: Demonstrates the default behavior of FastAPI to return JSON responses. This snippet shows a basic path operation that returns a Python dictionary, which FastAPI automatically serializes to JSON using `JSONResponse` with the `application/json` media type. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/advanced/custom-response.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_item(item_id: int): + return {"item_id": item_id, "name": "Awesome Item"} +``` + +---------------------------------------- + +TITLE: FastAPI Password Hashing and Validation +DESCRIPTION: This snippet illustrates a basic (pseudo) password hashing and validation mechanism. It checks if the provided password, after being 'hashed', matches the stored hashed password for the retrieved user. An `HTTPException` is raised if the passwords do not match, indicating an authentication failure. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/security/simple-oauth2.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import HTTPException, status + +# ... (within the login_for_access_token function, after user_dict is obtained) +# Assuming fake_hash_password is a function that 'hashes' the password +# and user_dict["hashed_password"] contains the stored hash. +if not fake_hash_password(form_data.password) == user_dict["hashed_password"]: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Incorrect username or password", + ) +``` + +---------------------------------------- + +TITLE: Python Function Without Type Hints +DESCRIPTION: This example shows a simple Python function that processes first and last names without any type annotations. It illustrates how the absence of type hints can limit editor assistance and make code harder to understand or validate. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +{!../../docs_src/python_types/tutorial001.py!} +``` + +---------------------------------------- + +TITLE: Example User Profile JSON Response +DESCRIPTION: This JSON object represents the typical data returned by a protected FastAPI endpoint, such as `/users/me/`, after a user has successfully authenticated. It includes basic user information like username, email, full name, and disabled status. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/security/oauth2-jwt.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + +---------------------------------------- + +TITLE: Declaring a Tuple and Set (Python 3.9+) +DESCRIPTION: This snippet demonstrates how to declare a tuple with specific types for each element and a set with a specific type for all elements using Python 3.9+ syntax. `tuple[int, int, str]` defines a tuple with two integers and a string, while `set[bytes]` defines a set containing bytes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +items_t: tuple[int, int, str] = (1, 2, "foo") +items_s: set[bytes] = {b"hallo", b"welt"} +``` + +---------------------------------------- + +TITLE: FastAPI Application with Item and Message Models +DESCRIPTION: Demonstrates a basic FastAPI application with Pydantic models for request and response bodies. It defines a POST endpoint for creating an item and a GET endpoint for a root message, showcasing how FastAPI automatically generates OpenAPI schema from these definitions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/generate-clients.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +class ResponseMessage(BaseModel): + message: str + + +app = FastAPI() + + +@app.post("/items/", response_model=ResponseMessage) +async def create_item(item: Item): + return {"message": "Item received"} + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} +``` + +---------------------------------------- + +TITLE: Initializing a Tuple and Set - Python 3.9+ +DESCRIPTION: This snippet initializes a tuple `items_t` with specific types for each element (int, int, str) and a set `items_s` where each element is of type bytes. It uses the built-in `tuple` and `set` types. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +items_t: tuple[int, int, str] = (1, 2, "foo") +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter String Validations +DESCRIPTION: Demonstrates how to add string validations to FastAPI query parameters using `Query`. This includes setting a minimum length (`min_length`) and defining a regular expression `pattern` for the parameter value, with examples referenced from external files. It also notes the deprecated `regex` parameter from Pydantic v1, shown via an external file reference. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} +``` + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} +``` + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/query_params_str_validations/tutorial004_regex_an_py310.py hl[11] *} +``` + +---------------------------------------- + +TITLE: Setting and Using Environment Variables (PowerShell) +DESCRIPTION: This snippet demonstrates how to set an environment variable named MY_NAME to "Wade Wilson" and then use it in a subsequent command to print a greeting. It shows the basic syntax for setting and accessing environment variables in PowerShell. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/environment-variables.md#_snippet_1 + +LANGUAGE: powershell +CODE: +``` +// 建立一個名為 MY_NAME 的環境變數 +$ $Env:MY_NAME = "Wade Wilson" + +// 在其他程式中使用它,例如 +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +---------------------------------------- + +TITLE: Setting and Using Environment Variables (Bash) +DESCRIPTION: This snippet demonstrates how to set an environment variable named MY_NAME to "Wade Wilson" and then use it in a subsequent command to print a greeting. It shows the basic syntax for setting and accessing environment variables in a Bash shell. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/environment-variables.md#_snippet_0 + +LANGUAGE: bash +CODE: +``` +// 你可以使用以下指令建立一個名為 MY_NAME 的環境變數 +$ export MY_NAME="Wade Wilson" + +// 然後,你可以在其他程式中使用它,例如 +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +---------------------------------------- + +TITLE: Initialize FastAPI Application Instance +DESCRIPTION: This snippet demonstrates the foundational steps for any FastAPI application: importing the `FastAPI` class and creating an instance of it. The `app` instance serves as the central object for defining all API routes and operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Creating OAuth2 Password Request Form in FastAPI +DESCRIPTION: This code snippet demonstrates how to create a FastAPI application with an endpoint that handles OAuth2 password requests. It uses `OAuth2PasswordRequestForm` to receive the username and password, which are then printed to the console. This is a basic setup and does not include actual authentication logic. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/security/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, FastAPI +from fastapi.security import OAuth2PasswordRequestForm + +app = FastAPI() + + +@app.post("/token") +async def login(form_data: OAuth2PasswordRequestForm = Depends()): + return {"username": form_data.username, "password": form_data.password} +``` + +---------------------------------------- + +TITLE: Importing BaseModel from Pydantic +DESCRIPTION: Imports the `BaseModel` class from the `pydantic` library. This is the base class for creating data models that define the structure and validation rules for request bodies. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel +``` + +---------------------------------------- + +TITLE: Example JSON Request Body (Optional Fields Omitted) +DESCRIPTION: Demonstrates a valid JSON object for the `Item` Pydantic model where optional fields (`description` and `tax`) are intentionally omitted. This showcases FastAPI's ability to handle partial request bodies gracefully, based on the model's definition of optional fields. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "price": 45.2 +} +``` + +---------------------------------------- + +TITLE: Usando BackgroundTasks no FastAPI +DESCRIPTION: Este snippet demonstra como importar BackgroundTasks e definir um parâmetro em uma função de operação de caminho para executar tarefas em segundo plano. O FastAPI cria o objeto BackgroundTasks e o passa como um parâmetro. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/background-tasks.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import BackgroundTasks, FastAPI + +app = FastAPI() + + +async def write_notification(email: str, message=""): + with open("log.txt", mode="w") as f: + f.write(f"notification for {email}: {message}") + + +@app.post("/send-notification/{email}") +async def send_notification(email: str, background_tasks: BackgroundTasks): + background_tasks.add_task(write_notification, email, message="some notification") + return {"message": "Notification sent in the background"} +``` + +---------------------------------------- + +TITLE: Request Body with Examples +DESCRIPTION: Shows how to pass examples for the expected data in a request body using the `Body()` function. This allows for providing example data for the request body, which is then included in the generated JSON Schema and used in API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/schema-extra-example.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ], + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Combining Required, Default, and Optional Query Parameters in FastAPI +DESCRIPTION: This code snippet shows how to define a combination of required, default, and optional query parameters in a FastAPI endpoint. 'needy' is a required string, 'skip' is an integer with a default value of 0, and 'limit' is an optional integer. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/tr/docs/tutorial/query-params.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +@app.get("/items/{item_id}") +async def read_items( + item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None +): + item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} + return item +``` + +---------------------------------------- + +TITLE: Traefik Main Configuration File (`traefik.toml`) +DESCRIPTION: The `traefik.toml` file configures Traefik's basic settings, including defining an HTTP entry point on port `9999` and specifying `routes.toml` as the file provider for dynamic routing configurations. This setup allows Traefik to act as a proxy for the FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/behind-a-proxy.md#_snippet_3 + +LANGUAGE: TOML +CODE: +``` +[entryPoints] + [entryPoints.http] + address = ":9999" + +[providers] + [providers.file] + filename = "routes.toml" +``` + +---------------------------------------- + +TITLE: FastAPI OAuth2 Password Flow Basic Setup +DESCRIPTION: This snippet demonstrates the fundamental setup for OAuth2 password flow in FastAPI. It initializes `OAuth2PasswordBearer` with a `tokenUrl` parameter, which declares the endpoint where clients will send username and password to obtain a token. The `oauth2_scheme` is then integrated as a dependency for a path operation, allowing FastAPI to automatically generate security definitions in the OpenAPI documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/security/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Depends +from fastapi.security import OAuth2PasswordBearer + +app = FastAPI() + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +@app.get("/users/me") +async def read_users_me(token: str = Depends(oauth2_scheme)): + return {"token": token} +``` + +---------------------------------------- + +TITLE: FastAPI API Documentation Concepts: OpenAPI, Paths, and Operations +DESCRIPTION: This section defines key concepts central to building and documenting APIs with FastAPI, including the OpenAPI standard, the structure of API paths (endpoints), and the role of HTTP operations (methods) in defining API interactions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: APIDOC +CODE: +``` +OpenAPI Specification: + - Standard: A specification for machine-readable interface files for describing, producing, consuming, and visualizing RESTful web services. + - Purpose: FastAPI uses OpenAPI to automatically generate interactive API documentation (Swagger UI, ReDoc) and enable client code generation. + - Components: + - "Schema": A general term for a named entity or key/value pair. + - "Operation Schema": Describes how to interact with an API endpoint (e.g., parameters, responses). + - "Data Schema": Describes the structure of data (e.g., JSON models), often using JSON Schema. + +Paths (Endpoints): + - Definition: The last part of a URL after the domain, representing a specific resource or functionality (e.g., `/items/foo`). + - Synonyms: Often referred to as "URLs" or "routes". + +Operations (HTTP Methods): + - Definition: Standard HTTP methods used to perform actions on resources. Each path can have one or more operations associated with it. + - Common Operations: + - `POST`: Used for creating new data. + - `GET`: Used for reading/retrieving data. + - `PUT`: Used for updating/replacing existing data. + - `DELETE`: Used for removing data. + - Other Operations: `OPTIONS`, `HEAD`, `PATCH`, `TRACE`. + - Role in FastAPI: Each operation is typically mapped to a Python function (path operation function) that handles requests for that specific method and path. +``` + +---------------------------------------- + +TITLE: FastAPI Response Attribute Access Example (Price) +DESCRIPTION: A partial code snippet demonstrating accessing the `price` attribute from an `item` object within a FastAPI response dictionary. This illustrates the flexibility of changing accessed attributes and the editor's ability to recognize types. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_12 + +LANGUAGE: Python +CODE: +``` + ... "item_price": item.price ... +``` + +---------------------------------------- + +TITLE: Define a Simple FastAPI Path Operation for Testing +DESCRIPTION: This Python code defines a basic FastAPI path operation that returns a simple JSON message. This endpoint can be used to verify the application's general functionality and ensure that the static file serving configuration does not interfere with regular API routes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/how-to/custom-docs-ui-assets.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +@app.get("/hello") +async def read_hello(): + return {"message": "Hello from FastAPI!"} +``` + +---------------------------------------- + +TITLE: Basic Dockerfile for FastAPI Application +DESCRIPTION: This Dockerfile defines the steps to containerize a FastAPI application. It starts from a Python base image, sets the working directory, and efficiently installs dependencies by leveraging Docker's build cache. Finally, it copies the application code and specifies the command to run the Uvicorn server. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/deployment/docker.md#_snippet_0 + +LANGUAGE: Dockerfile +CODE: +``` +FROM python:3.9 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt + +RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY ./app /code/app + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] +``` + +---------------------------------------- + +TITLE: 단순 타입 힌트 예제 +DESCRIPTION: 이 예제는 `str`, `int`, `float`, `bool`, `bytes`와 같은 파이썬 표준 타입을 변수에 적용하는 방법을 보여줍니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +name: str +age: int +price: float +is_adult: bool +data: bytes +``` + +---------------------------------------- + +TITLE: Importing BaseModel from Pydantic +DESCRIPTION: This code snippet demonstrates how to import the `BaseModel` class from the `pydantic` module. This is the base class for creating data models that define the structure and validation rules for request bodies in FastAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI +from pydantic import BaseModel +``` + +---------------------------------------- + +TITLE: Create a Simple Background Task Function +DESCRIPTION: Illustrates how to define a standard Python function that can serve as a background task. This function can be either `async def` or `def` and can accept parameters. The example simulates writing a notification to a log file, which is a common use case for background processing. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/background-tasks.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +def write_notification(email: str, message: str = ""): + with open("log.txt", mode="a") as email_file: + content = f"notification for {email}: {message}\n" + email_file.write(content) +``` + +---------------------------------------- + +TITLE: Declare Python Types and Pydantic Models +DESCRIPTION: Demonstrates how to declare variables with type hints using standard Python types and define data models using Pydantic's BaseModel, including `str`, `int`, and `datetime.date`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# Declare a variable as a str +# and get editor support inside the function +def main(user_id: str): + return user_id + + +# A Pydantic model +class User(BaseModel): + id: int + name: str + joined: date +``` + +---------------------------------------- + +TITLE: Injeção de dependência com BackgroundTasks +DESCRIPTION: Este snippet demonstra como usar BackgroundTasks com injeção de dependência. Um parâmetro do tipo BackgroundTasks pode ser declarado em vários níveis: em uma função de operação de caminho, em uma dependência, em uma subdependência, etc. O FastAPI reutiliza o mesmo objeto para todas as tarefas em segundo plano. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/background-tasks.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +from typing import Optional + +from fastapi import BackgroundTasks, Depends, FastAPI + +app = FastAPI() + + +def write_log(message: str): + with open("log.txt", mode="a") as log: + log.write(message) + + +def get_query(background_tasks: BackgroundTasks, q: Optional[str] = None): + if q: + message = f"found query {q}\n" + background_tasks.add_task(write_log, message) + return q + + +@app.post("/send-notification/{email}") +async def send_notification( + email: str, + background_tasks: BackgroundTasks, + q: str = Depends(get_query), +): + message = f"message to {email}\n" + background_tasks.add_task(write_log, message) + return {"message": "Notification sent in the background"} +``` + +---------------------------------------- + +TITLE: Pydantic Model Example (Python 3.10+) +DESCRIPTION: This example demonstrates a Pydantic model definition with type annotations. It shows how to define a class with attributes and their corresponding types, enabling data validation and conversion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_19 + +LANGUAGE: Python +CODE: +``` +{!> ../../docs_src/python_types/tutorial011_py310.py!} +``` + +---------------------------------------- + +TITLE: Import Query from FastAPI +DESCRIPTION: This snippet imports the `Query` class from the `fastapi` module. `Query` is used to define additional validations and metadata for query parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/query-params-str-validations.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Import FastAPI HTTP and WebSocket Exceptions +DESCRIPTION: This snippet demonstrates the standard way to import the `HTTPException` and `WebSocketException` classes directly from the `fastapi` library. These classes are essential for raising structured HTTP and WebSocket errors within your FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/exceptions.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import HTTPException, WebSocketException +``` + +---------------------------------------- + +TITLE: FastAPI application initialization in main.py +DESCRIPTION: This snippet shows the typical structure of a FastAPI application's main entry point. It imports FastAPI, creates an instance of the app, and defines a simple path operation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/testing.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} +``` + +---------------------------------------- + +TITLE: Pydantic Model Example (Python 3.9+) +DESCRIPTION: This example demonstrates a Pydantic model definition with type annotations. It shows how to define a class with attributes and their corresponding types, enabling data validation and conversion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_20 + +LANGUAGE: Python +CODE: +``` +{!> ../../docs_src/python_types/tutorial011_py39.py!} +``` + +---------------------------------------- + +TITLE: Declaring a Tuple and Set (Python 3.8+) +DESCRIPTION: This snippet demonstrates how to declare a tuple with specific types for each element and a set with a specific type for all elements using the `typing` module in Python 3.8+. `Tuple[int, int, str]` defines a tuple with two integers and a string, while `Set[bytes]` defines a set containing bytes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from typing import Tuple, Set + +items_t: Tuple[int, int, str] = (1, 2, "foo") +items_s: Set[bytes] = {b"hallo", b"welt"} +``` + +---------------------------------------- + +TITLE: Markdown Admonition Syntax Example (English) +DESCRIPTION: Illustrates the standard Markdown admonition syntax used in the FastAPI documentation for 'tip' blocks. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/management-tasks.md#_snippet_1 + +LANGUAGE: Markdown +CODE: +``` +/// tip + +This is a tip. + +/// +``` + +---------------------------------------- + +TITLE: Install FastAPI with standard dependencies +DESCRIPTION: Command to install FastAPI with its standard dependencies. This is suitable for production applications where specific optional features might be installed separately to minimize dependencies. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/index.md#_snippet_2 + +LANGUAGE: Shell +CODE: +``` +pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Creating a Data Model with Pydantic +DESCRIPTION: This code snippet shows how to define a data model using Pydantic's `BaseModel`. The model defines the structure of the expected JSON request body, including data types and optional fields. It inherits from `BaseModel` and uses standard Python type annotations for attributes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None +``` + +---------------------------------------- + +TITLE: Importing BaseModel from Pydantic +DESCRIPTION: This code snippet demonstrates how to import the `BaseModel` class from the `pydantic` library. `BaseModel` is used as the base class for defining data models in FastAPI applications. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel +``` + +---------------------------------------- + +TITLE: Declare Typed List (Python 3.9+ Syntax) +DESCRIPTION: This example illustrates the modern Python 3.9+ syntax for declaring a list where all elements are of a specific type, such as `str`. This provides a concise and clear way to specify homogeneous list types. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +my_list: list[str] +``` + +---------------------------------------- + +TITLE: Declare Examples for Pydantic Fields +DESCRIPTION: Illustrates how to add example data directly to individual fields within a Pydantic model using the `Field()` function's `examples` argument. This allows for field-specific examples in the generated JSON Schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/schema-extra-example.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel, Field + +class Item(BaseModel): + name: str = Field(examples=["Foo"]) + description: str | None = Field(default=None, examples=["A very nice Item"]) + price: float = Field(examples=[35.4]) + tax: float | None = Field(default=None, examples=[3.2]) +``` + +---------------------------------------- + +TITLE: Importing BaseModel from Pydantic +DESCRIPTION: This code snippet demonstrates how to import the `BaseModel` class from the `pydantic` library. `BaseModel` is used as the base class for creating data models that define the structure and validation rules for request bodies in FastAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from pydantic import BaseModel +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter Default Values with Validations +DESCRIPTION: Shows how to combine default values with string validations for FastAPI query parameters. This example, referenced from an external file, illustrates setting a `min_length` and a specific default value for a query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/query_params_str_validations/tutorial005_an_py39.py hl[9] *} +``` + +---------------------------------------- + +TITLE: FastAPI Parameter Example Declaration (APIDOC) +DESCRIPTION: This section details how to declare examples for various FastAPI parameters (`Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, `File`) using both JSON Schema `examples` and OpenAPI-specific `openapi_examples` for improved documentation UI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/schema-extra-example.md#_snippet_6 + +LANGUAGE: APIDOC +CODE: +``` +FastAPI Parameter Example Declaration + +This section details how to declare examples for various FastAPI parameters (Path, Query, Header, Cookie, Body, Form, File) using both JSON Schema `examples` and OpenAPI-specific `openapi_examples` for improved documentation UI. + +1. JSON Schema `examples` (for `Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, `File`): + - Purpose: To embed examples directly into the generated JSON Schema for a parameter. + - Usage: Pass a `list` of example `dict`s to the `examples` argument of the parameter function (e.g., `Body(examples=[...])`). + - Limitation: Swagger UI may not fully support displaying multiple `examples` from JSON Schema. + +2. OpenAPI-specific `openapi_examples` (for `Path`, `Query`, `Header`, `Cookie`, `Body`, `Form`, `File`): + - Purpose: To provide examples that are part of the OpenAPI specification's path operation details, specifically designed for display in documentation UIs like Swagger UI. + - Usage: Pass a `dict` of named examples to the `openapi_examples` argument of the parameter function (e.g., `Body(openapi_examples={...})`). + - Structure of each named example (value in the `openapi_examples` dict): + - `summary` (string): A short description for the example. + - `description` (string, optional): A longer description that can contain Markdown text. + - `value` (any): The actual example data (e.g., a JSON object for a body, a string for a query parameter). + - `externalValue` (string, optional): A URL pointing to the example data, alternative to `value`. Support may vary across tools. + - Benefit: Fully supported by Swagger UI for displaying multiple examples. +``` + +---------------------------------------- + +TITLE: Function with Type Hints +DESCRIPTION: This Python function uses type hints to specify that the `first_name` and `last_name` parameters should be strings. This enables better code completion and error checking in editors. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +def get_full_name(first_name: str, last_name: str): + return first_name.title() + " " + last_name.title() +``` + +---------------------------------------- + +TITLE: Function with Type Hints and Error +DESCRIPTION: This example demonstrates how type hints can help identify errors in your code. The editor can detect that an integer is being used where a string is expected. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +def get_name_with_age(name: str, age: int): + name_with_age = name + " is this old: " + age + return name_with_age +``` + +---------------------------------------- + +TITLE: Declare Request Body Examples in FastAPI +DESCRIPTION: Illustrates how to define single and multiple example payloads for a request body in FastAPI using the `Body()` dependency. These examples are incorporated into the JSON Schema and can be viewed in the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/schema-extra-example.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated + +from fastapi import FastAPI, Body +from pydantic import BaseModel + +app = FastAPI() + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Annotated[ + Item, + Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2 + } + ] + ) + ] +): + results = {"item_id": item_id, "item": item} + return results +``` + +LANGUAGE: Python +CODE: +``` +from typing import Annotated + +from fastapi import FastAPI, Body +from pydantic import BaseModel + +app = FastAPI() + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Annotated[ + Item, + Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2 + }, + { + "name": "Bar", + "price": 42.0, + "description": "The Bar Fighters", + "tax": 3.2 + }, + { + "name": "Baz", + "price": 50.0, + "tax": 10.0 + } + ] + ) + ] +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Declare Request Body Single Example using FastAPI Body +DESCRIPTION: This method demonstrates how to provide a single example for a request body using FastAPI's `Body()` dependency. The example data will be displayed in the OpenAPI documentation for the endpoint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/schema-extra-example.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI, Body +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + example={ + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Pydantic Model Example (Python 3.8+) +DESCRIPTION: This example demonstrates a Pydantic model definition with type annotations. It shows how to define a class with attributes and their corresponding types, enabling data validation and conversion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_21 + +LANGUAGE: Python +CODE: +``` +{!> ../../docs_src/python_types/tutorial011.py!} +``` + +---------------------------------------- + +TITLE: FastAPI: Data Filtering with Pydantic Inheritance and Return Type Annotations +DESCRIPTION: Demonstrates an advanced pattern using Pydantic model inheritance (`UserIn` inherits from `BaseUser`) and function return type annotations (`-> BaseUser`). This allows the function to return a more comprehensive object (`UserIn`) while FastAPI automatically filters the response to conform to the `BaseUser` schema, providing both strong typing for tooling and effective data filtering. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/response-model.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class BaseUser(BaseModel): + username: str + + +class UserIn(BaseUser): + password: str + + +@app.post("/user/") +async def create_user(user: UserIn) -> BaseUser: + return user +``` + +---------------------------------------- + +TITLE: Example JSON Response from FastAPI Endpoint +DESCRIPTION: A sample JSON output demonstrating the structure of a response from the `/items/{item_id}` endpoint when accessed with specific parameters, showing the item ID and query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/index.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: FastAPI: Declare Required Parameters with Ellipsis Default +DESCRIPTION: This example demonstrates the previous method of declaring required path, query, cookie, and header parameters in FastAPI by explicitly setting their default value to `...` (Ellipsis). This ensures the parameters are mandatory for the endpoint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_23 + +LANGUAGE: Python +CODE: +``` +from fastapi import Cookie, FastAPI, Header, Path, Query + +app = FastAPI() + + +@app.get("/items/{item_id}") +def main( + item_id: int = Path(default=..., gt=0), + query: str = Query(default=..., max_length=10), + session: str = Cookie(default=..., min_length=3), + x_trace: str = Header(default=..., title="Tracing header"), +): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Importing List from Typing Module (Python < 3.9) +DESCRIPTION: Shows how to import `List` from Python's `typing` module, which is necessary for declaring lists with type parameters in Python versions prior to 3.9. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import List +``` + +---------------------------------------- + +TITLE: Declaring Request Body Examples (JSON Schema) +DESCRIPTION: Shows how to provide example data for a request body using FastAPI's `Body()` function. This method utilizes the JSON Schema `examples` array, allowing for single or multiple examples to be embedded directly into the body's schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/schema-extra-example.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ] + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +LANGUAGE: Python +CODE: +``` +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + { + "name": "Bar", + "price": 42.0, + "description": "The Bar Fighters", + }, + { + "name": "Baz", + "price": 50.5, + "tax": 10.5, + "description": "There goes my baz", + }, + ] + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Import BaseSettings for Pydantic Settings +DESCRIPTION: Shows the updated import path for `BaseSettings` when using Pydantic for settings management, now from the dedicated `pydantic_settings` package. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_14 + +LANGUAGE: Python +CODE: +``` +from pydantic_settings import BaseSettings +``` + +---------------------------------------- + +TITLE: Instantiate Pydantic Models from Data +DESCRIPTION: This example shows two ways to instantiate a Pydantic BaseModel: directly with keyword arguments and by unpacking a dictionary using the `**` operator. Unpacking a dictionary is useful when data comes from an external source, like a database or an API request, and needs to be validated against the model schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/features.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary",", + "joined": "2018-11-30" +} + +my_second_user: User = User(**second_user_data) +``` + +---------------------------------------- + +TITLE: Adding String Validations to FastAPI Query Parameters +DESCRIPTION: Demonstrates how to apply string validations like `max_length` and `min_length` to query parameters using FastAPI's `Query()` dependency. These validations are automatically enforced and documented in the OpenAPI schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/query-params-str-validations.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Query +from typing import Union + +app = FastAPI() + +@app.get("/items/") +async def read_items( + q_max: Union[str, None] = Query(default=None, max_length=50), + q_min_max: str = Query(min_length=3, max_length=50) +): + """ + Example endpoint demonstrating query parameter string validations. + - q_max: Optional query parameter with a maximum length of 50. + - q_min_max: Required query parameter with a minimum length of 3 and maximum length of 50. + """ + return {"q_max": q_max, "q_min_max": q_min_max} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI Application +DESCRIPTION: Creates an instance of the FastAPI class, which serves as the main entry point for building the API. This instance is then used to define the API's endpoints and handle incoming requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Declare Field Example using Pydantic Field Arguments +DESCRIPTION: This method allows adding an example to individual fields within a Pydantic model by passing an `example` argument directly to `Field()`. These extra arguments are primarily for documentation purposes and do not add validation rules. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/schema-extra-example.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from pydantic import BaseModel, Field + + +class Item(BaseModel): + name: str = Field(example="Foo") + description: Union[str, None] = Field(default=None, example="A very nice Item") + price: float = Field(example=35.4) + tax: Union[float, None] = Field(default=None, example=3.2) +``` + +---------------------------------------- + +TITLE: Pydantic BaseSettings for Application Configuration +DESCRIPTION: Pydantic's `BaseSettings` provides a robust way to manage application settings by automatically loading values from environment variables, supporting type validation, and default values. It's ideal for handling both general configuration and sensitive data like API keys. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/settings.md#_snippet_3 + +LANGUAGE: APIDOC +CODE: +``` +Pydantic BaseSettings: + Inherit from `pydantic.BaseSettings` to define application settings. + Automatically loads values from environment variables (case-insensitive matching, e.g., APP_NAME maps to app_name). + Supports type hints for validation and default values. + + Definition Example: + from pydantic import BaseSettings, Field + + class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = Field(50, ge=10, le=500) + api_key: str # Example for sensitive data + + Usage Example: + settings = Settings() + # Access settings as attributes: + # settings.app_name + # settings.admin_email + # settings.items_per_user + # settings.api_key + + Environment Variable Mapping: + - Fields are mapped from environment variables by name (case-insensitive). + - E.g., `APP_NAME` environment variable populates `app_name` field. + - Crucial for sensitive data: `API_KEY` environment variable populates `api_key` field. +``` + +---------------------------------------- + +TITLE: Upgrade FastAPI app with Pydantic model and PUT endpoint +DESCRIPTION: This updated FastAPI application demonstrates how to define a Pydantic `BaseModel` for request body validation and how to implement a `PUT` endpoint. The `update_item` function receives an `item_id` path parameter and an `Item` model as the request body, showcasing data validation and serialization capabilities. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: List 타입 힌트 예제 +DESCRIPTION: `typing` 모듈에서 `List`를 임포트하여 문자열 리스트에 대한 타입 힌트를 선언하는 방법을 보여줍니다. 이를 통해 에디터는 리스트의 아이템을 처리할 때 도움을 줄 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from typing import List +``` + +LANGUAGE: python +CODE: +``` +items: List[str] +``` + +---------------------------------------- + +TITLE: Initializing a List with String Type - Python 3.9+ +DESCRIPTION: This snippet initializes a list named `items` where each element is a string. It uses the built-in `list` type with type parameters to specify the type of elements within the list. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +items: list[str] = ["foo", "bar"] +``` + +---------------------------------------- + +TITLE: FastAPI User Data Response Example +DESCRIPTION: Example JSON response for retrieving user data from an authenticated endpoint like `/users/me`. It showcases typical user attributes such as username, email, full name, disabled status, and a placeholder for a hashed password. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/simple-oauth2.md#_snippet_7 + +LANGUAGE: JSON +CODE: +``` +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" +} +``` + +---------------------------------------- + +TITLE: Add Type Hints to Python Function Parameters +DESCRIPTION: This snippet shows how to add type hints to function parameters using colons (`:`). By specifying `str` for `first_name` and `last_name`, code editors can provide intelligent autocompletion and type checking, significantly improving developer productivity. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/python-types.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +def get_full_name(first_name: str, last_name: str): + return f"{first_name.title()} {last_name.title()}" +``` + +---------------------------------------- + +TITLE: Creating a Data Model with Pydantic +DESCRIPTION: This code snippet shows how to define a data model using Pydantic's `BaseModel`. The model defines the structure of the expected JSON request body, including data types and optional fields. Default values can be assigned to make fields optional. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None +``` + +---------------------------------------- + +TITLE: Query Parameter with Default Value and Minimum Length +DESCRIPTION: This snippet defines a query parameter `q` with a default value and a minimum length validation. The `Query` class is used to specify both the default value and the `min_length` constraint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/query-params-str-validations.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: str = Query("fixedquery", min_length=3)): + return {"q": q} +``` + +---------------------------------------- + +TITLE: Defining Base and Inherited Pydantic Models in FastAPI +DESCRIPTION: This code defines a base Pydantic model `UserBase` and inherits from it to create `UserCreate`, `UserUpdate`, and `User` models. This approach reduces code duplication by sharing common attributes and validations among related models. The `User` model includes an `id` field, while `UserCreate` and `UserUpdate` handle password variations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/extra-models.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class UserBase(BaseModel): + email: str + first_name: str + last_name: str + is_active: bool = True + + +class UserCreate(UserBase): + password: str + + +class UserUpdate(UserBase): + password: Optional[str] = None + + +class User(UserBase): + id: int +``` + +---------------------------------------- + +TITLE: FastAPI: Using Generic `list` for Query Parameters +DESCRIPTION: Illustrates using the generic `list` type hint for query parameters instead of `typing.List[str]`. While simpler, FastAPI won't perform type checking on the list's contents with this approach. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: list = Query(default=[])): + query_items = {"q": q} + return query_items +``` + +---------------------------------------- + +TITLE: Declaring a Dictionary (Python 3.9+) +DESCRIPTION: This snippet demonstrates how to declare a dictionary with string keys and float values using Python 3.9+ syntax. The type hint `dict[str, float]` specifies that the keys are strings and the values are floats. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +prices: dict[str, float] = {"apple": 1.5, "banana": 0.75} +``` + +---------------------------------------- + +TITLE: Define a Pydantic Data Model for Request Body +DESCRIPTION: Defines a Pydantic `BaseModel` class named `Item` that specifies the expected structure and types for an incoming JSON request body. Attributes like `description` and `tax` are marked as optional using `None` as their default value, demonstrating how to handle optional fields. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None +``` + +---------------------------------------- + +TITLE: Check String Prefix with Tuple using startswith() +DESCRIPTION: Illustrates a Python string method startswith()'s capability to accept a tuple of prefixes. This allows checking if a string begins with any of the provided prefixes in a single, concise call, improving readability and efficiency for multiple prefix checks. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_21 + +LANGUAGE: Python +CODE: +``` +# Example within a validation function: +# v is the string to validate +if not v.startswith(("isbn-", "imdb-")): + # Handle validation error + pass +``` + +---------------------------------------- + +TITLE: Declare Multiple Request Body Examples using FastAPI Body +DESCRIPTION: This method shows how to provide multiple examples for a request body using FastAPI's `Body()` dependency. Each example is defined as a dictionary with `summary`, `description`, `value`, and optionally `externalValue` fields, enhancing the OpenAPI documentation with various scenarios. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/schema-extra-example.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI, Body +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item working just fine.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "bad_name": { + "summary": "Bad name example", + "description": "An item with a **bad name**.", + "value": { + "name": "Foobar", + "price": 35.4, + }, + }, + "long_description": { + "summary": "Long description example", + "description": "This item has a **long description**.", + "value": { + "name": "Bar", + "price": 35.4, + "description": "The King of the Foo", + "tax": 3.2, + }, + }, + }, + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Request Body with Multiple Examples +DESCRIPTION: Demonstrates how to pass multiple examples for the expected data in a request body using the `Body()` function. This allows for providing multiple example datasets for the request body, which are then included in the generated JSON Schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/schema-extra-example.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + examples=[ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + { + "name": "Bar", + "price": 99.99, + "description": "The best item there is", + }, + ], + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Declare Query Parameters with Pydantic Models in FastAPI +DESCRIPTION: This snippet demonstrates how to define complex query parameters using a Pydantic `BaseModel`. It allows for structured validation, default values, and type annotations for multiple query parameters, enhancing API robustness and clarity. The `Field` function is used for advanced validation like `gt` (greater than) and `le` (less than or equal to). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from typing import Annotated, Literal + +from fastapi import FastAPI, Query +from pydantic import BaseModel, Field + +app = FastAPI() + + +class FilterParams(BaseModel): + limit: int = Field(100, gt=0, le=100) + offset: int = Field(0, ge=0) + order_by: Literal["created_at", "updated_at"] = "created_at" + tags: list[str] = [] + + +@app.get("/items/") +async def read_items(filter_query: Annotated[FilterParams, Query()]): + return filter_query +``` + +---------------------------------------- + +TITLE: Including General Utilities and CLI Framework (Python) +DESCRIPTION: Specifies common utility libraries like PyYAML for data serialization and Typer for building command-line interfaces. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/requirements-docs.txt#_snippet_2 + +LANGUAGE: Python +CODE: +``` +typer == 0.15.3 +pyyaml >=5.3.1,<7.0.0 +``` + +---------------------------------------- + +TITLE: Defining Python Types with Pydantic for Data Models +DESCRIPTION: This snippet demonstrates how to define standard Python types, including a Pydantic BaseModel, for data modeling. It shows a function parameter with a type hint and a Pydantic class `User` with typed attributes like `id` (int), `name` (str), and `joined` (date), enabling editor support and data validation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/features.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# Declare a variable as a str +# and get editor support inside the function +def main(user_id: str): + return user_id + + +# A Pydantic model +class User(BaseModel): + id: int + name: str + joined: date +``` + +---------------------------------------- + +TITLE: Upgrade FastAPI app with Pydantic model and PUT endpoint +DESCRIPTION: This updated FastAPI application demonstrates how to define a Pydantic `BaseModel` for request body validation and how to implement a `PUT` endpoint. The `update_item` function receives an `item_id` path parameter and an `Item` model as the request body, showcasing data validation and serialization capabilities. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/README.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Example JSON response from FastAPI GET endpoint +DESCRIPTION: This snippet shows the expected JSON response when accessing the `/items/{item_id}` endpoint with a path parameter and an optional query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/README.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Example JSON Response +DESCRIPTION: Example JSON response from the /items/{item_id} endpoint with a query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/index.md#_snippet_5 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Initializing FastAPI Application +DESCRIPTION: Creates an instance of the FastAPI class, which serves as the entry point for building the API. The `app` variable is used to define and interact with the API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Initializing a List with String Type - Python 3.8+ +DESCRIPTION: This snippet initializes a list named `items` where each element is a string. It uses the `List` type from the `typing` module to specify the type of elements within the list. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import List + +items: List[str] = ["foo", "bar"] +``` + +---------------------------------------- + +TITLE: Update main.py to accept PUT request body - Python +DESCRIPTION: This code snippet updates the `main.py` file to handle a PUT request to the `/items/{item_id}` endpoint. It defines a request body using a Pydantic model `Item` and updates the `update_item` function to accept an `item` of type `Item`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/he/docs/index.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: FastAPI Main App File +DESCRIPTION: This is an example of a FastAPI application defined in main.py. It defines a simple GET endpoint that returns a JSON response. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/testing.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: FastAPI Application with Pydantic Model and PUT Request +DESCRIPTION: Updated FastAPI application demonstrating how to define a Pydantic `BaseModel` for request body validation and how to implement a `PUT` endpoint that accepts a structured request body, enhancing API data handling. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/index.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Python Type Hints for List of Pydantic Models +DESCRIPTION: These Python snippets demonstrate how to declare a function parameter that expects a JSON array as its outermost element. FastAPI leverages these type hints to automatically parse and validate a list of Pydantic model instances. The `List[Image]` syntax is for older Python versions, while `list[Image]` is for Python 3.9 and above. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-nested-models.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +images: List[Image] +``` + +LANGUAGE: Python +CODE: +``` +images: list[Image] +``` + +---------------------------------------- + +TITLE: Handling Exceptions and Cleanup in FastAPI Dependencies with Yield +DESCRIPTION: This section illustrates patterns for managing resources and exceptions within FastAPI dependencies that utilize `yield`. The first example shows how a dependency can catch and re-raise `HTTPException` while ensuring session rollback and closure. The second example demonstrates a general `try/finally` pattern to guarantee cleanup operations are executed, irrespective of exceptions occurring after `yield`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_26 + +LANGUAGE: Python +CODE: +``` +async def get_database(): + with Session() as session: + try: + yield session + except HTTPException: + session.rollback() + raise + finally: + session.close() +``` + +LANGUAGE: Python +CODE: +``` +async def do_something(): + try: + yield something + finally: + some_cleanup() +``` + +---------------------------------------- + +TITLE: Markdown Admonition Syntax Example (Spanish Translation) +DESCRIPTION: Shows how a 'tip' admonition block is translated into Spanish, maintaining the original 'tip' keyword. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/management-tasks.md#_snippet_2 + +LANGUAGE: Markdown +CODE: +``` +/// tip + +Esto es un consejo. + +/// +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies and Run CLI +DESCRIPTION: This snippet demonstrates the new way to install FastAPI with its standard dependencies using `pip install "fastapi[standard]"` and how to invoke the FastAPI CLI directly via `python -m fastapi`. This change simplifies dependency management and provides a direct entry point for CLI operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_6 + +LANGUAGE: bash +CODE: +``` +pip install "fastapi[standard]" +``` + +LANGUAGE: bash +CODE: +``` +python -m fastapi +``` + +---------------------------------------- + +TITLE: Define Pydantic BaseSettings class for application settings +DESCRIPTION: Examples of defining a `Settings` class using Pydantic's `BaseSettings` to manage application configuration from environment variables. This includes declaring variables with type annotations and optional default values, demonstrating both Pydantic v1 and v2 import paths. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/settings.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 +``` + +LANGUAGE: python +CODE: +``` +from pydantic import BaseSettings + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 +``` + +---------------------------------------- + +TITLE: Multiple Body and Query Parameters in FastAPI +DESCRIPTION: Demonstrates how to declare multiple body parameters along with query parameters in a FastAPI endpoint. Scalar values are interpreted as query parameters by default. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body-multiple-params.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI, Body + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +class User(BaseModel): + username: str + full_name: Union[str, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item, + user: User, + importance: int = Body(), + q: Union[str, None] = None, +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + results.update({"item": item, "user": user, "importance": importance}) + return results +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: This command installs FastAPI along with its standard dependencies, which typically include `uvicorn` for running the server and `python-multipart` for form parsing. It's crucial to put `"fastapi[standard]"` in quotes to ensure it works correctly across different terminal environments. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/README.md#_snippet_0 + +LANGUAGE: Shell +CODE: +``` +$ pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Mixing Path, Query, and Body Parameters in FastAPI +DESCRIPTION: Demonstrates how to declare optional body parameters by assigning a default value of None. This example shows how to define an endpoint that accepts an optional Item model in the request body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body-multiple-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI, Body + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Union[Item, None] = Body(default=None), + q: Union[str, None] = None, +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + if item: + results.update({"item": item}) + return results +``` + +---------------------------------------- + +TITLE: Creating a Background Task Function +DESCRIPTION: This code snippet shows how to define a standard function to be executed as a background task. The function can receive parameters and perform operations such as writing to a file. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/background-tasks.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +def write_notification(email: str, message=""): + with open("log.txt", mode="w") as f: + f.write(f"notification for {email}: {message}") +``` + +---------------------------------------- + +TITLE: FastAPI Endpoint JSON Response Example +DESCRIPTION: This JSON snippet illustrates the typical response structure returned by the `/items/{item_id}` endpoint in the FastAPI application. It shows how path and query parameters are reflected in the JSON output. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/index.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Function with Type Hints and Error +DESCRIPTION: This function demonstrates how type hints can help catch errors. The `age` parameter is incorrectly used as a string, leading to a type error that an editor can detect. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +def get_name_with_age(name: str, age: int): + return name + " is " + age +``` + +---------------------------------------- + +TITLE: Run FastAPI Application with Uvicorn +DESCRIPTION: This console command initiates the Uvicorn server to host the FastAPI application. `main:app` specifies the Python module (`main.py`) and the FastAPI instance (`app`). The `--reload` flag enables automatic server restarts upon code changes, which is highly beneficial during development. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: console +CODE: +``` +uvicorn main:app --reload +``` + +---------------------------------------- + +TITLE: Path, Query, and Request Body Parameters in FastAPI +DESCRIPTION: Illustrates how to declare path, query, and request body parameters simultaneously in a FastAPI endpoint. FastAPI intelligently determines the source of each parameter based on its type annotation and presence in the path. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/body/tutorial004.py hl[18] *} +``` + +---------------------------------------- + +TITLE: Define Path Operation Decorator +DESCRIPTION: The @app.get("/") decorator tells FastAPI that the function below is in charge of handling requests that go to: the path / using a get operation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +``` + +---------------------------------------- + +TITLE: Combining Required, Default, and Optional Query Parameters +DESCRIPTION: This code snippet demonstrates how to define a combination of required, default, and optional query parameters in a FastAPI endpoint. 'needy' is required, 'skip' has a default value of 0, and 'limit' is optional. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/query-params.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_item( + item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None +): + item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} + return item +``` + +---------------------------------------- + +TITLE: FastAPI Dependency Hierarchy Diagram +DESCRIPTION: A Mermaid graph illustrating the hierarchical dependency injection structure in FastAPI, showing how different API endpoints (`/items/public/`, `/items/private/`, `/users/{user_id}/activate`, `/items/pro/`) can depend on various user types (current_user, active_user, admin_user, paying_user) through a chain of dependencies. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/index.md#_snippet_4 + +LANGUAGE: mermaid +CODE: +``` +graph TB + +current_user(["current_user"]) +active_user(["active_user"]) +admin_user(["admin_user"]) +paying_user(["paying_user"]) + +public["/items/public/"] +private["/items/private/"] +activate_user["/users/{user_id}/activate"] +pro_items["/items/pro/"] + +current_user --> active_user +active_user --> admin_user +active_user --> paying_user + +current_user --> public +active_user --> private +admin_user --> activate_user +paying_user --> pro_items +``` + +---------------------------------------- + +TITLE: Example JSON response from FastAPI GET endpoint +DESCRIPTION: This snippet shows the expected JSON response when accessing the `/items/{item_id}` endpoint with a path parameter and an optional query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: This command installs FastAPI along with its standard dependencies, which typically include `uvicorn` for running the server and `python-multipart` for form parsing. It's crucial to put `"fastapi[standard]"` in quotes to ensure it works correctly across different terminal environments. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_0 + +LANGUAGE: Shell +CODE: +``` +$ pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Declaring String Variable with Editor Support in Python +DESCRIPTION: This code snippet demonstrates how to declare a string variable with type hints in Python, enabling editor support for autocompletion and type checking. It defines a simple function `main` that takes a string `user_id` as input and returns it. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/features.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# 宣告一個變數為 string +# 並在函式中獲得 editor support +def main(user_id: str): + return user_id +``` + +---------------------------------------- + +TITLE: Creating an async FastAPI application +DESCRIPTION: This Python code defines a simple FastAPI application with two asynchronous endpoints: `/` which returns a greeting, and `/items/{item_id}` which returns the item ID and an optional query parameter. It imports FastAPI, creates an app instance, and defines the endpoints using `async def`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/index.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Example JSON Structure for Nested Pydantic Model +DESCRIPTION: This JSON snippet provides an example of the expected data structure when a Pydantic model includes a nested submodel. It shows how the `image` field contains its own set of properties, reflecting the defined `Image` submodel. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_8 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +---------------------------------------- + +TITLE: Import Path and Annotated for FastAPI Path Parameters +DESCRIPTION: This snippet demonstrates the necessary imports for defining path parameters with validation and metadata in FastAPI. It imports `Annotated` from `typing` and `FastAPI`, `Path` from `fastapi`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params-numeric-validations.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated +from fastapi import FastAPI, Path + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Function Parameter with Optional Type +DESCRIPTION: Illustrates a function parameter defined as `Optional[str]`, which means it can accept either a string or `None` as a value. The parameter is still required if no default value is provided. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_15 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +def say_hi(name: Optional[str]): + if name: + print(f"Hi {name}") + else: + print("Hello World") +``` + +---------------------------------------- + +TITLE: Declare OpenAPI-Specific Examples in FastAPI +DESCRIPTION: Explains how to use the `openapi_examples` parameter with FastAPI dependencies like `Body()` to provide multiple, rich examples that are displayed in the Swagger UI. This leverages OpenAPI's specific `examples` field, which supports additional metadata like `summary` and `description` for each example. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/schema-extra-example.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated + +from fastapi import FastAPI, Body +from pydantic import BaseModel + +app = FastAPI() + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Annotated[ + Item, + Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item working correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2 + } + }, + "bad_tax": { + "summary": "A bad tax example", + "description": "Tax can't be more than price.", + "value": { + "name": "Bar", + "price": 35.4, + "tax": 35.41 + } + } + } + ) + ] +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: This command demonstrates the current recommended way to install FastAPI, explicitly including its standard optional dependencies. Previously, these dependencies were installed by default, but now they require explicit inclusion using the `[standard]` extra. This change addresses user feedback regarding unwanted default dependencies and provides more control over the installation footprint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_7 + +LANGUAGE: Shell +CODE: +``` +pip install "fastapi[standard]" +``` + +LANGUAGE: Shell +CODE: +``` +pip install fastapi +``` + +---------------------------------------- + +TITLE: Example Initial Request Body to FastAPI +DESCRIPTION: A sample JSON payload sent by an external user to the FastAPI application. This body contains an invoice ID, customer details, and total, which can be referenced in callback path expressions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/openapi-callbacks.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{ + "id": "2expen51ve", + "customer": "Mr. Richie Rich", + "total": "9999" +} +``` + +---------------------------------------- + +TITLE: Define a reusable dependency function in FastAPI +DESCRIPTION: This Python function, `common_parameters`, serves as a dependency. It accepts optional query parameters `q` (string), `skip` (integer, default 0), and `limit` (integer, default 100), similar to a path operation function. It processes these parameters and returns them as a dictionary, demonstrating how dependencies can encapsulate shared logic and provide data to other parts of the application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/index.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} +``` + +---------------------------------------- + +TITLE: FastAPI: Declare Required Parameters by Omitting Default Value +DESCRIPTION: This example illustrates the new FastAPI feature allowing required path, query, cookie, and header parameters to be declared by simply omitting their default value. This aligns with Pydantic's behavior for required fields, making parameter declaration more concise and intuitive. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_24 + +LANGUAGE: Python +CODE: +``` +from fastapi import Cookie, FastAPI, Header, Path, Query + +app = FastAPI() + + +@app.get("/items/{item_id}") +def main( + item_id: int = Path(gt=0), + query: str = Query(max_length=10), + session: str = Cookie(min_length=3), + x_trace: str = Header(title="Tracing header"), +): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: 타입 힌트 추가 예제 +DESCRIPTION: 이 함수는 `first_name`과 `last_name` 매개변수에 타입 힌트를 추가하여 문자열임을 명시합니다. 이를 통해 에디터에서 자동 완성 및 오류 검사를 지원받을 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +def get_full_name(first_name: str, last_name: str): + full_name = first_name.title() + " " + last_name.title() + return full_name +``` + +---------------------------------------- + +TITLE: FastAPI Required and Optional Query Parameters +DESCRIPTION: Explains how to declare query parameters as required or optional in FastAPI. It covers simple required parameters, optional parameters with a `None` default, and how to make a parameter required even if it can accept `None` as a valid value, forcing the client to send it, with examples including references to external files. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +q: str +``` + +LANGUAGE: Python +CODE: +``` +q: str | None = None +``` + +LANGUAGE: Python +CODE: +``` +q: Annotated[str | None, Query(min_length=3)] = None +``` + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} +``` + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} +``` + +---------------------------------------- + +TITLE: Fixing Type Error with str() +DESCRIPTION: This code fixes the type error by converting the integer `age` to a string using `str(age)`. This ensures that the function concatenates strings correctly. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +def get_name_with_age(name: str, age: int): + return name + " is " + str(age) +``` + +---------------------------------------- + +TITLE: 클래스 타입 힌트 예제 +DESCRIPTION: 변수의 타입으로 클래스를 선언하는 방법을 보여줍니다. `Person` 클래스를 정의하고 변수를 `Person` 타입으로 선언하여 에디터의 도움을 받을 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_9 + +LANGUAGE: python +CODE: +``` +class Person: + def __init__(self, name: str): + self.name = name +``` + +LANGUAGE: python +CODE: +``` +first_person: Person +``` + +---------------------------------------- + +TITLE: FastAPI Security Dependencies for Active User Authentication +DESCRIPTION: Demonstrates how to create FastAPI dependencies (`get_current_user`, `get_current_active_user`) to retrieve and validate the current authenticated user. These dependencies raise `HTTPException` for unauthenticated or inactive users, ensuring secure access to protected endpoints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/security/simple-oauth2.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from typing import Annotated, Optional + +# Assume UserInDB and fake_users_db are defined as in the previous snippet +class UserInDB: + def __init__(self, username: str, hashed_password: str, email: Optional[str] = None, full_name: Optional[str] = None, disabled: Optional[bool] = None): + self.username = username + self.hashed_password = hashed_password + self.email = email + self.full_name = full_name + self.disabled = disabled + +fake_users_db = { + "johndoe": UserInDB(username="johndoe", hashed_password="secret", full_name="John Doe", disabled=False), + "janedoe": UserInDB(username="janedoe", hashed_password="anothersecret", full_name="Jane Doe", disabled=True), +} + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): + # In a real app, decode JWT or validate token securely + user = fake_users_db.get(token) # Simplified: token is just username here + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authentication credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + return user + +async def get_current_active_user( + current_user: Annotated[UserInDB, Depends(get_current_user)] +): + if current_user.disabled: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user") + return current_user + +# Example of a protected endpoint +# @router.get("/users/me/") +# async def read_users_me(current_user: Annotated[UserInDB, Depends(get_current_active_user)]): +# return current_user +``` + +---------------------------------------- + +TITLE: FastAPI HTTP Update Operations: PUT and PATCH +DESCRIPTION: This section details the usage and behavior of HTTP PUT and PATCH methods for updating resources in FastAPI applications. It outlines their distinct purposes, common pitfalls, and recommended Pydantic techniques for handling data updates effectively. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-updates.md#_snippet_5 + +LANGUAGE: APIDOC +CODE: +``` +HTTP PUT Method: + Purpose: Used for full replacement of a resource. + Behavior: + - Replaces the entire resource with the data provided in the request body. + - If fields are omitted from the request body, they will be replaced by their default values (if defined in the Pydantic model) or removed if no default is specified. + - Example: Updating an item with a PUT request that omits the 'tax' field will cause 'tax' to revert to its default value (e.g., 10.5) if it was previously stored with a different value (e.g., 20.2). + FastAPI Implementation: + - Use `@app.put("/path/{item_id}")` decorator. + - Employ `fastapi.encoders.jsonable_encoder` to convert Pydantic models to JSON-compatible dictionaries before storing. + +HTTP PATCH Method: + Purpose: Used for partial updates of a resource. + Behavior: + - Applies incremental modifications to a resource, updating only the fields provided in the request body. + - Fields not included in the request body remain unchanged. + FastAPI Implementation: + - Use `@app.patch("/path/{item_id}")` decorator. + - Recommended Pydantic techniques for partial updates: + - `item.model_dump(exclude_unset=True)`: Generates a dictionary containing only the fields explicitly set in the incoming request, ignoring default values. (Pydantic v1: `.dict(exclude_unset=True)`) + - `stored_item_model.model_copy(update=update_data)`: Creates a new model instance by copying an existing one and applying the partial `update_data` dictionary. (Pydantic v1: `.copy(update=update_data)`) + - Workflow: Retrieve stored data -> Load into Pydantic model -> Generate update dict with `exclude_unset` -> Apply updates with `model_copy` -> Encode and save. +``` + +---------------------------------------- + +TITLE: Request Body, Path, and Query Parameters in FastAPI +DESCRIPTION: Illustrates how to declare request body, path parameters, and query parameters simultaneously in a FastAPI endpoint. FastAPI automatically infers the source of each parameter based on its type and declaration. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def create_item(item_id: int, item: Item, q: Union[str, None] = None): + results = {"item_id": item_id, **item.dict()} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Example requirements.txt file +DESCRIPTION: This is an example of a requirements.txt file. It lists the packages and their versions that are required for the project. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/virtual-environments.md#_snippet_16 + +LANGUAGE: txt +CODE: +``` +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +---------------------------------------- + +TITLE: Define a Pydantic Model with a Typed List Field (List[str]) +DESCRIPTION: Shows how to explicitly declare a Pydantic model field as a list of a specific type (e.g., `List[str]`) using `typing.List`. This ensures strict type validation for list elements and provides better documentation. Includes an example of a standalone typed list declaration. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-nested-models.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import List +``` + +LANGUAGE: Python +CODE: +``` +from typing import List + +my_list: List[str] +``` + +LANGUAGE: Python +CODE: +``` +from typing import List +from pydantic import BaseModel + +class Item(BaseModel): + name: str + tags: List[str] +``` + +---------------------------------------- + +TITLE: Demonstrate Type Hinting for Error Detection +DESCRIPTION: This example illustrates how type hints enable static analysis tools and IDEs to detect potential type-related errors before runtime. The function expects an integer for `age`, and if `age` were used directly in string concatenation without conversion, a type error would be flagged by a type checker. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/python-types.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +def get_name_and_age(name: str, age: int): + # This function expects 'age' to be an integer. + # If 'age' were used directly in string concatenation without conversion, + # a type error would be flagged by a type checker. + return f"Hello, {name}. You are {age} years old." +``` + +---------------------------------------- + +TITLE: Defining a Set Field +DESCRIPTION: Demonstrates how to define a set field in a Pydantic model. Sets are used to store unique elements. The `tags` attribute is defined as a set of strings. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-nested-models.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: set[str] = set() +``` + +---------------------------------------- + +TITLE: Base Model for User Data +DESCRIPTION: Defines a base Pydantic model UserBase with common fields and then creates specialized models UserIn, User, and UserInDB inheriting from it. This reduces code duplication and ensures consistency across different user models. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/extra-models.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class UserBase(BaseModel): + username: str + email: str + full_name: Optional[str] = None + + +class UserIn(UserBase): + password: str + + +class User(UserBase): + pass + + +class UserInDB(UserBase): + hashed_password: str +``` + +---------------------------------------- + +TITLE: JSON Response Example +DESCRIPTION: This JSON snippet shows an example response from the /items/{item_id} endpoint, including the item_id and the query parameter q. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/bn/docs/index.md#_snippet_5 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Declaring a Dictionary (Python 3.8+) +DESCRIPTION: This snippet demonstrates how to declare a dictionary with string keys and float values using the `typing` module in Python 3.8+. The type hint `Dict[str, float]` specifies that the keys are strings and the values are floats. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_10 + +LANGUAGE: Python +CODE: +``` +from typing import Dict + +prices: Dict[str, float] = {"apple": 1.5, "banana": 0.75} +``` + +---------------------------------------- + +TITLE: Importing Path from FastAPI +DESCRIPTION: This code snippet shows how to import the `Path` class from the `fastapi` library. This is necessary to declare path parameters with validations and metadata. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/path-params-numeric-validations.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI, Path +``` + +---------------------------------------- + +TITLE: Function Parameter with Optional Type (Python 3.10+) +DESCRIPTION: Illustrates a function parameter defined using the `|` operator in Python 3.10+, indicating it can accept either a string or `None`. The parameter is still required if no default value is provided. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_16 + +LANGUAGE: Python +CODE: +``` +def say_hi(name: str | None): + if name: + print(f"Hi {name}") + else: + print("Hello World") +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter List with Default Values +DESCRIPTION: Demonstrates how to provide a default list of values for a query parameter when no values are supplied in the URL. This example uses Python 3.9+ type hints (`List[str]`). It includes the Python path operation and an example JSON response showing the default list being used. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from typing import List +from fastapi import FastAPI, Query + +app = FastAPI() + +@app.get("/items/") +async def read_items(q: List[str] = Query(default=["foo", "bar"])): + return {"q": q} +``` + +LANGUAGE: JSON +CODE: +``` +{ + "q": [ + "foo", + "bar" + ] +} +``` + +---------------------------------------- + +TITLE: FastAPI Application Initialization +DESCRIPTION: Demonstrates the initial steps to set up a FastAPI application: importing the `FastAPI` class and creating an instance of it. This instance serves as the main entry point for defining API routes and functionalities. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Import Query and Annotated for Parameter Validation +DESCRIPTION: Imports necessary components for advanced parameter validation in FastAPI. `Query` is imported from `fastapi` to define query parameter specific validations, and `Annotated` is imported from `typing` (for Python 3.10+) or `typing_extensions` (for Python 3.8+) to add metadata to type hints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/query-params-str-validations.md#_snippet_1 + +LANGUAGE: Python 3.10+ +CODE: +``` +from typing import Annotated +from fastapi import FastAPI, Query +``` + +LANGUAGE: Python 3.8+ +CODE: +``` +from fastapi import FastAPI, Query +from typing_extensions import Annotated +``` + +---------------------------------------- + +TITLE: Importing List from typing +DESCRIPTION: Shows how to import the `List` type from the `typing` module in Python versions prior to 3.9. This is necessary for declaring lists with specific element types. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-nested-models.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import List +``` + +---------------------------------------- + +TITLE: Import Query for Parameter Validations +DESCRIPTION: To apply advanced validations and metadata to query parameters in FastAPI, the `Query` class must be imported from the `fastapi` module. This class is essential for defining validation rules like length constraints or regular expressions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/query-params-str-validations.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import Query +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter Definition: `Annotated` vs. Direct Default +DESCRIPTION: Compares the recommended `Annotated` approach for defining FastAPI query parameters with the older method of using `Query` directly as a function parameter's default. It highlights the clarity and benefits of `Annotated` for type checking and tool support, showing both correct and incorrect usage patterns. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/query-params-str-validations.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +q: Annotated[str, Query(default="rick")] = "morty" +``` + +LANGUAGE: Python +CODE: +``` +q: Annotated[str, Query()] = "rick" +``` + +LANGUAGE: Python +CODE: +``` +q: str = Query(default="rick") +``` + +---------------------------------------- + +TITLE: Initializing FastAPI Application with Async +DESCRIPTION: This code initializes a FastAPI application with asynchronous route handlers using `async def`. It includes two GET endpoints: one for the root path ('/') and another for retrieving items by ID ('/items/{item_id}'). It demonstrates how to define path parameters and optional query parameters in an asynchronous context. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/index.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Type Hinting Example in Python +DESCRIPTION: This code demonstrates the use of type hints in Python for function parameters and return values. It shows how to declare a variable as a string with editor autocompletion support. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from datetime import date +from pydantic import BaseModel + +# Оголошення змінної як str +# з підтримкою автодоповнення у редакторі +def main(user_id: str): + return user_id + +# Модель Pydantic +class User(BaseModel): + id: int + name: str + joined: date +``` + +---------------------------------------- + +TITLE: Multiple Body and Query Parameters +DESCRIPTION: Illustrates how to combine body parameters with query parameters in a FastAPI endpoint. Query parameters are automatically inferred if they are simple types and not explicitly defined as Body parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-multiple-params.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item, + user: User, + importance: int = Body(..., gt=0), + q: Union[str, None] = None +): + results = {"item_id": item_id, "item": item, "user": user, "importance": importance} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: FastAPI Standard Response Classes (File, HTML, JSON, PlainText, Redirect, Base, Streaming) +DESCRIPTION: This API documentation details the standard response classes available in FastAPI, which are largely inherited from Starlette. These classes offer diverse ways to construct and return HTTP responses, including serving files, HTML content, JSON data, plain text, handling redirects, and streaming data. Most classes share common attributes for status, media type, headers, and cookie management, with `StreamingResponse` uniquely utilizing a `body_iterator` for its content. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/responses.md#_snippet_2 + +LANGUAGE: APIDOC +CODE: +``` +fastapi.responses.FileResponse: + Description: Response class for serving files. + Members: + - chunk_size: Size of chunks to read from the file. + - charset: Character set for the response. + - status_code: HTTP status code. + - media_type: Media type (Content-Type header). + - body: The response body content. + - background: Background task to run after sending the response. + - raw_headers: Raw HTTP headers as a list of byte tuples. + - render: Method to render the response body. + - init_headers: Method to initialize response headers. + - headers: Response headers dictionary. + - set_cookie: Method to set a cookie in the response. + - delete_cookie: Method to delete a cookie from the response. + +fastapi.responses.HTMLResponse: + Description: Response class for serving HTML content. + Members: + - charset: Character set for the response. + - status_code: HTTP status code. + - media_type: Media type (Content-Type header). + - body: The response body content. + - background: Background task to run after sending the response. + - raw_headers: Raw HTTP headers as a list of byte tuples. + - render: Method to render the response body. + - init_headers: Method to initialize response headers. + - headers: Response headers dictionary. + - set_cookie: Method to set a cookie in the response. + - delete_cookie: Method to delete a cookie from the response. + +fastapi.responses.JSONResponse: + Description: Response class for serving JSON data. + Members: + - charset: Character set for the response. + - status_code: HTTP status code. + - media_type: Media type (Content-Type header). + - body: The response body content. + - background: Background task to run after sending the response. + - raw_headers: Raw HTTP headers as a list of byte tuples. + - render: Method to render the response body. + - init_headers: Method to initialize response headers. + - headers: Response headers dictionary. + - set_cookie: Method to set a cookie in the response. + - delete_cookie: Method to delete a cookie from the response. + +fastapi.responses.PlainTextResponse: + Description: Response class for serving plain text. + Members: + - charset: Character set for the response. + - status_code: HTTP status code. + - media_type: Media type (Content-Type header). + - body: The response body content. + - background: Background task to run after sending the response. + - raw_headers: Raw HTTP headers as a list of byte tuples. + - render: Method to render the response body. + - init_headers: Method to initialize response headers. + - headers: Response headers dictionary. + - set_cookie: Method to set a cookie in the response. + - delete_cookie: Method to delete a cookie from the response. + +fastapi.responses.RedirectResponse: + Description: Response class for HTTP redirects. + Members: + - charset: Character set for the response. + - status_code: HTTP status code. + - media_type: Media type (Content-Type header). + - body: The response body content. + - background: Background task to run after sending the response. + - raw_headers: Raw HTTP headers as a list of byte tuples. + - render: Method to render the response body. + - init_headers: Method to initialize response headers. + - headers: Response headers dictionary. + - set_cookie: Method to set a cookie in the response. + - delete_cookie: Method to delete a cookie from the response. + +fastapi.responses.Response: + Description: Base response class for custom responses. + Members: + - charset: Character set for the response. + - status_code: HTTP status code. + - media_type: Media type (Content-Type header). + - body: The response body content. + - background: Background task to run after sending the response. + - raw_headers: Raw HTTP headers as a list of byte tuples. + - render: Method to render the response body. + - init_headers: Method to initialize response headers. + - headers: Response headers dictionary. + - set_cookie: Method to set a cookie in the response. + - delete_cookie: Method to delete a cookie from the response. + +fastapi.responses.StreamingResponse: + Description: Response class for streaming data. + Members: + - body_iterator: An async iterator for the response body. + - charset: Character set for the response. + - status_code: HTTP status code. + - media_type: Media type (Content-Type header). + - body: The response body content. + - background: Background task to run after sending the response. + - raw_headers: Raw HTTP headers as a list of byte tuples. + - render: Method to render the response body. + - init_headers: Method to initialize response headers. + - headers: Response headers dictionary. + - set_cookie: Method to set a cookie in the response. + - delete_cookie: Method to delete a cookie from the response. +``` + +---------------------------------------- + +TITLE: 타입 힌트를 이용한 오류 수정 예제 +DESCRIPTION: 이 함수는 `name`과 `age`를 입력받아 문자열을 반환합니다. `age`를 문자열로 변환하여 타입 오류를 수정합니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +def create_item(name: str, age: int): + return {"name": name, "age": str(age)} +``` + +---------------------------------------- + +TITLE: Implement Dependency Injection in FastAPI Path Operation +DESCRIPTION: Demonstrates how to integrate the `common_parameters` dependency into a FastAPI path operation. By using `common: dict = Depends(common_parameters)`, FastAPI ensures that the `common_parameters` function is executed and its return value is injected into the `common` argument of the `read_items` path operation function, promoting code reusability and modularity. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/dependencies/index.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from fastapi import FastAPI, Depends + +app = FastAPI() + +# This function serves as a dependency +def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} + +@app.get("/items/") +async def read_items(common: dict = Depends(common_parameters)): + """ + Reads items using common parameters injected via dependency. + """ + return common +``` + +---------------------------------------- + +TITLE: Define Synchronous GET Path Operation for Root +DESCRIPTION: As an alternative to asynchronous functions, this snippet illustrates defining a synchronous GET endpoint for the root path ('/') using a regular `def` function. This approach is suitable for operations that do not involve I/O-bound tasks. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/first-steps.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Declaring Examples with Pydantic Field +DESCRIPTION: Illustrates how to add `examples` directly to individual fields within a Pydantic model using the `Field()` function. These examples are included in the generated JSON Schema for the respective field. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/schema-extra-example.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel, Field + +class Item(BaseModel): + name: str = Field(examples=["Foo"]) + description: str | None = Field( + default=None, examples=["A very nice Item"] + ) + price: float = Field(examples=[35.4, 40.2]) + tax: float | None = Field(default=None, examples=[3.2, 3.5]) +``` + +---------------------------------------- + +TITLE: Creating a Data Model with Pydantic +DESCRIPTION: Defines a data model named `Item` by inheriting from `BaseModel`. It includes fields like `name`, `description`, `price`, and `tax`, with type annotations and default values to specify data types and optional fields. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None +``` + +---------------------------------------- + +TITLE: Mixing Path, Query, and Body Parameters +DESCRIPTION: Demonstrates how to mix Path, Query, and body parameters in a FastAPI route. It also shows how to declare body parameters as optional by setting a default value of None. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-multiple-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Union[Item, None] = None, + q: Union[str, None] = None +): + results = {"item_id": item_id} + if item: + results.update({"item": item}) + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: FastAPI Core Features and Design Principles +DESCRIPTION: This section details the fundamental design principles and key features of the FastAPI framework. It covers its adherence to open standards, automatic documentation generation, reliance on Python type hints for data validation and editor support, robust security mechanisms, and a powerful dependency injection system. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/features.md#_snippet_2 + +LANGUAGE: APIDOC +CODE: +``` +FastAPI Core Features: + +1. Based on Open Standards: + - Utilizes OpenAPI Specification for API definition, including endpoints, HTTP methods, parameters, responses, and security. + - Leverages JSON Schema for automatic data validation and documentation, as OpenAPI is built on JSON Schema. + - Enables automatic client code generation in many languages. + +2. Automatic Documentation: + - Provides interactive API documentation with Swagger UI, allowing direct testing and interaction with API endpoints from the browser. + - Offers alternative API documentation with ReDoc for a more concise and readable format. + +3. Standard Python Type Hints: + - Built entirely on standard Python 3.6+ type hints (PEP 484). + - No new syntax to learn; uses standard Python features. + - Integrates seamlessly with Pydantic for data validation and serialization. + +4. Editor Support: + - Provides excellent editor support (e.g., VS Code, PyCharm) for autocompletion, type checking, and error detection. + - Helps catch errors early and improves developer productivity by suggesting valid parameters and attributes. + +5. Data Validation: + - Automatically validates all data, including JSON, path parameters, query parameters, and headers. + - Supports complex data types like dictionaries, lists, and nested models. + - Validates specific formats (e.g., email, URL, UUID) and constraints (e.g., string length, number ranges). + - Powered by Pydantic for robust and efficient data validation. + +6. Security and Authentication: + - Supports various security schemes defined in OpenAPI. + - Includes HTTP Basic authentication. + - Integrates with OAuth2 (including JWT tokens) for advanced authentication. + - Supports API Keys (in headers, query parameters, cookies). + - Leverages Starlette's security utilities (e.g., session cookies). + - Provides reusable tools for integrating with databases, external APIs, and cloud services. + +7. Dependency Injection System: + - Features a highly advanced and powerful dependency injection system. + - Dependencies can have their own dependencies, forming a dependency graph. + - Handles all dependencies automatically, including extracting data from requests and injecting it into path operation functions. + - Automatically validates path operation function parameters based on their type hints. + - Supports creating database connections, security dependencies, and more. + - Prevents common errors related to resource management (e.g., database connections, web sockets). + +8. Limitless "Plug-in" Support: + - Designed to be highly extensible without imposing specific structures. + - Allows developers to write "plug-ins" for their applications by leveraging the dependency injection system and standard Python features. + +9. 100% Coverage: + - Achieves 100% test coverage for its codebase. + - Maintains 100% type annotation coverage, ensuring robust type checking and editor support. + - Used in production applications. +``` + +---------------------------------------- + +TITLE: Using BackgroundTasks with FastAPI Dependency Injection +DESCRIPTION: Demonstrates the integration of `BackgroundTasks` with FastAPI's dependency injection system. Tasks can be added from within dependencies or the path operation function itself, and FastAPI ensures all registered tasks are merged and executed after the response. This example shows tasks added from both a dependency and the main route, illustrating a robust pattern for complex background operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/background-tasks.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated + +from fastapi import BackgroundTasks, Depends, FastAPI + +app = FastAPI() + +def write_log(message: str): + with open("log.txt", mode="a") as log: + log.write(message) + +def get_query_background_tasks(background_tasks: BackgroundTasks, q: str | None = None): + if q: + background_tasks.add_task(write_log, f"query: {q}\n") + return q + +@app.post("/send-notification/{email}") +async def send_notification( + email: str, + background_tasks: BackgroundTasks, + q: Annotated[str | None, Depends(get_query_background_tasks)] = None, +): + background_tasks.add_task(write_log, f"message for {email}\n") + return {"message": "Notification sent in the background"} +``` + +---------------------------------------- + +TITLE: Mixing Path, Query, and Body Parameters in FastAPI +DESCRIPTION: This example shows how to mix `Path`, `Query`, and request body parameters in a FastAPI path operation function. It also demonstrates how to make a body parameter optional by setting its default value to `None`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-multiple-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Union[Item, None] = None, + q: Union[str, None] = None +): + results = {"item_id": item_id} + if item: + results.update(item.model_dump()) + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Query Parameter with Min and Max Length Validation +DESCRIPTION: This snippet defines a query parameter `q` with both minimum and maximum length validations using the `Query` class. The `min_length` and `max_length` parameters enforce the length constraints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/query-params-str-validations.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: Optional[str] = Query(None, min_length=3, max_length=50)): + return {"q": q} +``` + +---------------------------------------- + +TITLE: Return Data from Path Operation +DESCRIPTION: You can return a dict, list, singular values like str, int, etc. You can also return Pydantic models. There are many other objects and models that will be automatically converted to JSON. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: FastAPI Endpoint to Read Multiple Heroes +DESCRIPTION: Defines a GET endpoint `/heroes/` to retrieve a list of heroes from the database. It supports optional `offset` and `limit` query parameters for pagination, allowing clients to fetch a subset of heroes. The `limit` is capped at 100 to prevent excessively large responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/sql-databases.md#_snippet_7 + +LANGUAGE: python +CODE: +``` +from typing import List, Optional +from fastapi import APIRouter +from sqlmodel import select, Field +from .tutorial001_an_py310 import Hero, SessionDep # Assuming Hero and SessionDep are from the same file + +router = APIRouter() + +@router.get("/heroes/", response_model=List[Hero]) +def read_heroes( + offset: int = 0, + limit: Optional[int] = Field(default=None, le=100), + session: SessionDep +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes +``` + +---------------------------------------- + +TITLE: 타입 힌트를 이용한 오류 검출 예제 +DESCRIPTION: 이 함수는 `name`과 `age`를 입력받아 문자열을 반환합니다. `age`에 타입 힌트가 적용되어 있어 편집기에서 오류를 확인할 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +def create_item(name: str, age: int): + return {"name": name, "age": age} +``` + +---------------------------------------- + +TITLE: Import FastAPI Request Parameter Functions +DESCRIPTION: Demonstrates how to import the various request parameter functions (Body, Cookie, File, Form, Header, Path, Query) directly from the `fastapi` library. These functions are essential for defining how data is extracted from different parts of an HTTP request. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/parameters.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import Body, Cookie, File, Form, Header, Path, Query +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter Validation (Legacy Query as Default) +DESCRIPTION: Demonstrates the older method of defining optional query parameters and applying string validations (e.g., `max_length`) in FastAPI by using `Query` directly as the parameter's default value. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +q: str | None = Query(default=None) +``` + +LANGUAGE: Python +CODE: +``` +q: str | None = Query(default=None, max_length=50) +``` + +---------------------------------------- + +TITLE: Importing FastAPI +DESCRIPTION: This code snippet demonstrates how to import the FastAPI class, which provides the core functionality for building APIs. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: Initializing FastAPI App with Async Endpoints +DESCRIPTION: Creates a FastAPI application instance and defines two asynchronous GET endpoints: one for the root path ('/') and another for '/items/{item_id}' with a path parameter and an optional query parameter. It uses the FastAPI library and returns JSON responses using async def. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/index.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Inheriting Models with FastAPI and Pydantic +DESCRIPTION: Demonstrates how to inherit from Pydantic models to create more complex data structures in FastAPI. This allows for code reuse and easier management of related data models. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/body-nested-models.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI +from pydantic import BaseModel, HttpUrl + +app = FastAPI() + + +class Image(BaseModel): + url: HttpUrl + name: str + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list[str] = [] + image: Optional[Image] = None + + +class Offer(BaseModel): + name: str + description: Optional[str] = None + price: float + items: list[Item] + + +@app.post("/offers/") +async def create_offer(offer: Offer): + return offer +``` + +---------------------------------------- + +TITLE: FastAPI PATCH Endpoint for Partial Item Updates +DESCRIPTION: A comprehensive FastAPI endpoint demonstrating how to perform partial updates using the HTTP PATCH method. It involves retrieving existing data, creating a Pydantic model from it, generating an update dictionary with `exclude_unset`, merging updates with `model_copy`, and finally encoding the updated model for storage. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-updates.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +@app.patch("/items/{item_id}") +async def update_item_partial(item_id: str, item: Item): + if item_id not in items: + raise HTTPException(status_code=404, detail="Item not found") + stored_item_data = items[item_id] + stored_item_model = Item(**stored_item_data) + update_data = item.model_dump(exclude_unset=True) + updated_item = stored_item_model.model_copy(update=update_data) + items[item_id] = jsonable_encoder(updated_item) + return updated_item +``` + +---------------------------------------- + +TITLE: Import Security from FastAPI +DESCRIPTION: This code snippet demonstrates how to import the `Security` function from the `fastapi` library. `Security` is used in scenarios where you need to handle dependencies that also involve declaring OAuth2 scopes for authorization and authentication. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/dependencies.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import Security +``` + +---------------------------------------- + +TITLE: Declare Pydantic Model Attributes with Field Validation +DESCRIPTION: This code illustrates how to define attributes within a Pydantic `BaseModel` class using `Field` to apply validation rules and include metadata. Each attribute is assigned a type, an optional default value, and a `Field` instance to specify constraints such as minimum length, maximum length, or value ranges, enhancing data integrity. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-fields.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +class Item(BaseModel): + name: str = Field(min_length=3) + description: str | None = Field(default=None, max_length=300) + price: float = Field(gt=0) +``` + +---------------------------------------- + +TITLE: Install Python Multipart Package for FastAPI +DESCRIPTION: Installs the `python-multipart` package, which is essential for FastAPI to handle 'form data' used by OAuth2 for sending username and password. While included with `fastapi[standard]`, it requires manual installation if only `fastapi` is installed. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/first-steps.md#_snippet_0 + +LANGUAGE: console +CODE: +``` +pip install python-multipart +``` + +---------------------------------------- + +TITLE: Query Parameter List with Default Values +DESCRIPTION: This snippet shows how to define a query parameter that accepts a list of values and also provides a default list if no values are provided in the request. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/query-params-str-validations.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: List[str] = Query(["foo", "bar"])): + return {"q": q} +``` + +---------------------------------------- + +TITLE: Declaring Variables with Type Hints in Python +DESCRIPTION: This code demonstrates how to declare a variable with a type hint in Python using standard Python syntax. It shows how to define a function that accepts a string as input and returns a string, leveraging editor support for type checking and autocompletion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fa/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# Declare a variable as a str +# and get editor support inside the function +def main(user_id: str): + return user_id +``` + +---------------------------------------- + +TITLE: FastAPI Application File Structure with Explanations +DESCRIPTION: This snippet provides the same file structure as the previous example, but with inline comments explaining the role of each file and directory within the context of Python packages and modules, clarifying their import paths. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/bigger-applications.md#_snippet_1 + +LANGUAGE: text +CODE: +``` +. +├── app # "app" is a Python package +│   ├── __init__.py # this file makes "app" a "Python package" +│   ├── main.py # "main" module, e.g. import app.main +│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies +│   └── routers # "routers" is a "Python subpackage" +│   │ ├── __init__.py # makes "routers" a "Python subpackage" +│   │ ├── items.py # "items" submodule, e.g. import app.routers.items +│   │ └── users.py # "users" submodule, e.g. import app.routers.users +│   └── internal # "internal" is a "Python subpackage" +│   ├── __init__.py # makes "internal" a "Python subpackage" +│   └── admin.py # "admin" submodule, e.g. import app.internal.admin +``` + +---------------------------------------- + +TITLE: Define a Reusable FastAPI Dependency Function +DESCRIPTION: This Python function, `common_parameters`, serves as a reusable dependency. It accepts optional query parameters `q`, `skip`, and `limit`, and returns them as a dictionary. FastAPI will automatically resolve and inject these parameters when this dependency is used in a path operation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/dependencies/index.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} +``` + +---------------------------------------- + +TITLE: FastAPI Endpoint Using Pydantic Model as Output +DESCRIPTION: Illustrates a FastAPI `GET` endpoint (`/items/{item_id}`) that uses the `Item` Pydantic model as its `response_model`. This demonstrates how fields with default values, like `description`, are considered mandatory in the output schema because they will always have a value (even if `null`), leading to a separate OpenAPI output schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/how-to/separate-openapi-schemas.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.get("/items/{item_id}", response_model=Item) +async def read_item(item_id: str): + return {"name": "Foo", "price": 42} +``` + +---------------------------------------- + +TITLE: Defining Pydantic BaseSettings for Application Configuration +DESCRIPTION: Illustrates how to define a settings class using Pydantic's `BaseSettings`, allowing for type-hinted configuration variables with default values and automatic environment variable loading. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/settings.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +from pydantic import BaseSettings + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 +``` + +---------------------------------------- + +TITLE: Initializing FastAPI Instance +DESCRIPTION: Creates an instance of the FastAPI class, which serves as the core of the API application. This instance is used to define API endpoints and handle requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Pydantic Field with Examples +DESCRIPTION: Demonstrates how to declare examples for a Pydantic field using the `Field` function. This allows for providing example data directly within the field definition, which is then included in the generated JSON Schema and used in API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/schema-extra-example.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel, Field + +app = FastAPI() + + +class Item(BaseModel): + name: str = Field(examples=["Foo", "Bar"]) + description: Union[str, None] = Field(default=None, examples=["A very good item", "A great item"]) + price: float = Field(examples=[35.4, 99.99]) + tax: Union[float, None] = Field(default=None, examples=[3.2, 4.2]) + + +@app.post("/items/") +async def create_item(item: Item): + return item +``` + +---------------------------------------- + +TITLE: Optional 타입 힌트 예제 +DESCRIPTION: `Optional`을 사용하여 변수가 특정 타입이거나 `None`일 수 있음을 나타내는 방법을 보여줍니다. 이를 통해 에디터는 값이 `None`일 수 있는 경우에 대한 오류를 찾을 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_8 + +LANGUAGE: python +CODE: +``` +from typing import Optional + +name: Optional[str] = None +``` + +---------------------------------------- + +TITLE: Installing httpx for testing +DESCRIPTION: Shows how to install the httpx library, which is required for using TestClient. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/testing.md#_snippet_0 + +LANGUAGE: Shell +CODE: +``` +$ pip install httpx +``` + +---------------------------------------- + +TITLE: FastAPI OpenAPI-Specific Examples Structure +DESCRIPTION: Defines the structure and properties of the `openapi_examples` parameter used in FastAPI for `Path()`, `Query()`, `Header()`, `Cookie()`, `Body()`, `Form()`, and `File()` dependencies. These examples are specifically for OpenAPI and are used by tools like Swagger UI to display multiple rich examples for path operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/schema-extra-example.md#_snippet_5 + +LANGUAGE: APIDOC +CODE: +``` +Parameter: openapi_examples + Type: dict + Description: A dictionary where keys identify each example and values are dictionaries defining the example. + Each example dictionary can contain: + - summary: string (Short description for the example) + - description: string (A long description that can contain Markdown text) + - value: any (The actual example data, e.g., a dict) + - externalValue: string (URL pointing to the example, alternative to 'value') + Applicable to: Path(), Query(), Header(), Cookie(), Body(), Form(), File() +``` + +---------------------------------------- + +TITLE: Python Type Hinting and Pydantic Model Usage +DESCRIPTION: This snippet demonstrates how to define a Pydantic `BaseModel` for structured data validation and serialization, along with a simple Python function utilizing type hints for improved code clarity and editor support. It then shows how to instantiate this Pydantic model using both direct keyword arguments and dictionary unpacking. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# Declare a variable as a str +# and get editor support inside the function +def main(user_id: str): + return user_id + + +# A Pydantic model +class User(BaseModel): + id: int + name: str + joined: date +``` + +LANGUAGE: Python +CODE: +``` +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30" +} + +my_second_user: User = User(**second_user_data) +``` + +---------------------------------------- + +TITLE: FastAPI Endpoint to Create Hero +DESCRIPTION: Defines a POST endpoint `/heroes/` to create a new `Hero` entry in the database. It accepts a `Hero` object in the request body, adds it to the database session, commits the transaction, and refreshes the object to retrieve any database-generated values (like the `id`) before returning the created hero. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/sql-databases.md#_snippet_6 + +LANGUAGE: python +CODE: +``` +from fastapi import APIRouter +from sqlmodel import Session +from .tutorial001_an_py310 import Hero, SessionDep # Assuming Hero and SessionDep are from the same file + +router = APIRouter() + +@router.post("/heroes/", response_model=Hero) +def create_hero(hero: Hero, session: SessionDep): + session.add(hero) + session.commit() + session.refresh(hero) + return hero +``` + +---------------------------------------- + +TITLE: Example JSON Request Body for Nested Pydantic Models +DESCRIPTION: This JSON object illustrates a complex request body structure for a FastAPI application, demonstrating how Pydantic models handle nested data. It includes a list of 'images' objects, each with 'url' and 'name' fields, and a 'tags' array, showcasing the ability to parse and validate structured arrays of child models. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-nested-models.md#_snippet_0 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +---------------------------------------- + +TITLE: Custom Plain Text Error Response Example +DESCRIPTION: This snippet shows an example of a simplified, plain text error message that can be returned after overriding FastAPI's default exception handlers for validation errors. It provides a more concise output compared to the default JSON. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/handling-errors.md#_snippet_8 + +LANGUAGE: text +CODE: +``` +1 validation error +path -> item_id + value is not a valid integer (type=type_error.integer) +``` + +---------------------------------------- + +TITLE: Declare Query Parameters with Defaults in FastAPI +DESCRIPTION: Demonstrates how FastAPI automatically interprets function parameters not defined as path parameters as query parameters. This example shows how to define integer query parameters `skip` and `limit` with default values, allowing them to be optional in the URL and providing fallback values if not specified. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/") +async def read_items(skip: int = 0, limit: int = 10): + return {"skip": skip, "limit": limit} +``` + +---------------------------------------- + +TITLE: Path, Query, and Request Body Parameters in FastAPI +DESCRIPTION: This code snippet demonstrates how to declare a request body, path parameters, and query parameters within the same path operation in FastAPI. FastAPI automatically recognizes each of them and retrieves the correct data from the appropriate location. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Optional, Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def create_item(item_id: int, item: Item, q: Optional[str] = None): + results = {"item_id": item_id, **item.dict()} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Request Body and Path Parameters in FastAPI +DESCRIPTION: Shows how to declare both path parameters and a request body in a FastAPI endpoint. FastAPI automatically recognizes that function parameters matching path parameters should be taken from the path, while Pydantic models are taken from the request body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def create_item(item_id: int, item: Item): + return {"item_id": item_id, **item.dict()} +``` + +---------------------------------------- + +TITLE: Example FastAPI Application File Structure +DESCRIPTION: This snippet illustrates a typical directory layout for a larger FastAPI application, showcasing how files are organized into packages and subpackages to promote modularity. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/bigger-applications.md#_snippet_0 + +LANGUAGE: text +CODE: +``` +. +├── app +│   ├── __init__.py +│   ├── main.py +│   ├── dependencies.py +│   └── routers +│   │ ├── __init__.py +│   │ ├── items.py +│   │ └── users.py +│   └── internal +│   ├── __init__.py +│   └── admin.py +``` + +---------------------------------------- + +TITLE: 이름 결합 함수 예제 +DESCRIPTION: 이 함수는 `first_name`과 `last_name`을 입력받아 각 단어의 첫 글자를 대문자로 변환한 후 공백으로 연결하여 반환합니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +def get_full_name(first_name, last_name): + full_name = first_name.title() + " " + last_name.title() + return full_name + +print(get_full_name("john", "doe")) +``` + +---------------------------------------- + +TITLE: Example JSON Response from FastAPI GET Endpoint +DESCRIPTION: This JSON object represents a typical response from a FastAPI GET endpoint, specifically '/items/{item_id}?q=somequery'. It demonstrates how path parameters (item_id) and query parameters (q) are reflected in the API's output. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/index.md#_snippet_2 + +LANGUAGE: json +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Define Pydantic Data Model for Request Body +DESCRIPTION: Defines a Pydantic `Item` model by inheriting from `BaseModel`. This model specifies the expected structure and data types for incoming JSON request bodies. Fields like `description` and `tax` are marked as optional by assigning `None` as their default value. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None +``` + +---------------------------------------- + +TITLE: Tuple 및 Set 타입 힌트 예제 +DESCRIPTION: 이 예제는 `tuple`과 `set`에 대한 타입 힌트를 선언하는 방법을 보여줍니다. `typing` 모듈을 사용하여 각 요소의 타입을 지정할 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_6 + +LANGUAGE: python +CODE: +``` +from typing import Tuple, Set +``` + +LANGUAGE: python +CODE: +``` +items_t: Tuple[int, int, str] +items_s: Set[bytes] +``` + +---------------------------------------- + +TITLE: Налаштування метаданих API у FastAPI +DESCRIPTION: Приклад налаштування метаданих API, таких як title, summary, description, version, terms_of_service, contact, та license_info у додатку FastAPI. Він показує, як використовувати ці параметри для налаштування специфікації OpenAPI та автоматично згенерованих інтерфейсів документації API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/metadata.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI( + title="Fancy API", + summary="This is a fancy API for managing users and items.", + description=""" +This API is a **very fancy** one. + +It does _everything_. + +Trust me. +""", + version="0.1.0", + terms_of_service="http://example.com/terms/", + contact={ + "name": "Deadpoolio the Amazing", + "url": "http://example.com/contact/", + "email": "dp@example.com", + }, + license_info={ + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + }, +) + + +@app.get("/items/{item_id}") +async def read_item(item_id: str): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Example requirements.txt content +DESCRIPTION: An example of a `requirements.txt` file, specifying Python packages and their exact versions required for a project. This file ensures consistent dependency installation across different environments. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/virtual-environments.md#_snippet_10 + +LANGUAGE: requirements.txt +CODE: +``` +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +---------------------------------------- + +TITLE: Initializing FastAPI Application +DESCRIPTION: This code snippet demonstrates how to import the FastAPI class and create an instance of it. The FastAPI class provides all the functionality for defining an API. The 'app' variable will be the main interaction point for creating APIs. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Declaring OpenAPI Examples with openapi_examples in FastAPI +DESCRIPTION: This code snippet demonstrates how to declare OpenAPI-specific examples using the `openapi_examples` parameter in FastAPI for the `Item` model's `Body()`. It includes examples with summaries, descriptions, and values to be displayed in the documentation UI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/schema-extra-example.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "invalid": { + "summary": "Invalid data", + "description": "Data that doesn't pass validation.", + "value": { + "name": "Bar", + "price": "Twenty", + "tax": None, + }, + }, + }, + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Python Dictionary Unpacking for Class Instantiation +DESCRIPTION: Illustrates how the `**user_dict` syntax in Python unpacks a dictionary's key-value pairs into keyword arguments when instantiating a class or calling a function, providing a concise way to pass multiple arguments from a dictionary. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/simple-oauth2.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +UserInDB( + username = user_dict["username"], + email = user_dict["email"], + full_name = user_dict["full_name"], + disabled = user_dict["disabled"], + hashed_password = user_dict["hashed_password"] +) +``` + +---------------------------------------- + +TITLE: Declaring Request Body Examples (OpenAPI-Specific) +DESCRIPTION: Demonstrates how to use the `openapi_examples` parameter with FastAPI's `Body()` function to provide multiple named examples. These examples are directly embedded in the OpenAPI specification for the path operation and are typically rendered by documentation UIs like Swagger UI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/schema-extra-example.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item working just fine.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "bad_tax": { + "summary": "A bad tax example", + "description": "When the tax is too high, it's a bad example.", + "value": { + "name": "Bar", + "price": 42.0, + "tax": 200.0, + }, + }, + } + ), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Example JavaScript Code from ReDoc +DESCRIPTION: This is an example of the JavaScript code that might be served by ReDoc. It shows the beginning of the bundled JavaScript file. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/how-to/custom-docs-ui-assets.md#_snippet_7 + +LANGUAGE: JavaScript +CODE: +``` +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):... +``` + +---------------------------------------- + +TITLE: Combining Path, Query, and Body Parameters +DESCRIPTION: Demonstrates how to combine Path, Query, and request body parameters in a FastAPI endpoint. The `item` parameter is taken from the request body and is optional. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-multiple-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Union[Item, None] = None, + q: Union[str, None] = None +): + results = {"item_id": item_id} + if item: + results.update({"item": item}) + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: FastAPI Path Parameter Ordering Trick with `*` and Annotated +DESCRIPTION: This example demonstrates a Python trick using `*` to force subsequent parameters to be keyword-only, allowing flexible ordering of required parameters without `Annotated`. It also shows how `Annotated` simplifies this, making the `*` trick unnecessary by not relying on function parameter default values. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params-numeric-validations.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Path, Query + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_items( + *, + item_id: Path(title="The ID of the item to get"), + q: str +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results +``` + +LANGUAGE: Python +CODE: +``` +from typing import Annotated +from fastapi import FastAPI, Path, Query + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_items( + item_id: Annotated[int, Path(title="The ID of the item to get")], + q: str +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Importing Routers in FastAPI Main Application +DESCRIPTION: A conceptual example demonstrating how a main application file might import a router module from a subpackage, highlighting the standard Python import mechanism for modular code organization. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/bigger-applications.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from app.routers import items +``` + +---------------------------------------- + +TITLE: Declaring a List of Strings (Python 3.9+) +DESCRIPTION: This snippet demonstrates how to declare a variable as a list of strings using the built-in `list` type hint in Python 3.9 and later. It utilizes the `list[str]` syntax to specify that the variable `items` is a list where each element is a string. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +items: list[str] = ["foo", "bar"] +``` + +---------------------------------------- + +TITLE: Returning a Dictionary with Item Price +DESCRIPTION: This code snippet demonstrates how to return a dictionary containing the item price. It shows how to access the `price` attribute of an `item` object and include it in the returned dictionary. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/he/docs/index.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +... "item_name": item.name ... +``` + +LANGUAGE: Python +CODE: +``` +... "item_price": item.price ... +``` + +---------------------------------------- + +TITLE: Adding Specialized Tools for Search, Image Processing, and Formatting (Python) +DESCRIPTION: Includes packages for specific functionalities such as Chinese text segmentation (Jieba), image manipulation (Pillow, CairoSVG), and code formatting (Black). These are often used in conjunction with documentation generation or content processing. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/requirements-docs.txt#_snippet_3 + +LANGUAGE: Python +CODE: +``` +# For Material for MkDocs, Chinese search +jieba==0.42.1 +# For image processing by Material for MkDocs +pillow==11.1.0 +# For image processing by Material for MkDocs +cairosvg==2.7.1 +# For griffe, it formats with black +black==25.1.0 +``` + +---------------------------------------- + +TITLE: Importing FastAPI +DESCRIPTION: This code snippet shows how to import the FastAPI class from the fastapi package. The FastAPI class provides all the functionality needed to create an API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +FastAPI +``` + +---------------------------------------- + +TITLE: Define a Nested Pydantic Submodel (`Image`) +DESCRIPTION: This snippet illustrates the definition of a simple Pydantic submodel named `Image` with `url` and `name` fields. This submodel can then be used as a type for fields in other Pydantic models, enabling the creation of complex, nested data structures. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +class Image(BaseModel): + url: str + name: str +``` + +---------------------------------------- + +TITLE: Pydantic Model Copy with Update Parameter +DESCRIPTION: Illustrates how to create a new Pydantic model instance by copying an existing one and applying updates from a dictionary using `.model_copy(update=update_data)` (or `.copy(update=update_data)` for Pydantic v1). This method efficiently merges new data into an existing model. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-updates.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +stored_item_model.model_copy(update=update_data) +``` + +---------------------------------------- + +TITLE: Correct Markdown Admonition Keyword Translation with Pipe +DESCRIPTION: Provides the correct method for translating admonition keywords by using a pipe (`|`) to include the translated term while retaining the original keyword for styling. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/management-tasks.md#_snippet_4 + +LANGUAGE: Markdown +CODE: +``` +/// tip | consejo + +Esto es un consejo. + +/// +``` + +---------------------------------------- + +TITLE: Import FastAPI Class +DESCRIPTION: Demonstrates the standard way to import the `FastAPI` class from the `fastapi` library, which is the first step in creating a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/fastapi.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: Creating main.py with security features +DESCRIPTION: This code snippet demonstrates how to create a FastAPI application with OAuth2 password flow for user authentication. It includes defining an endpoint to receive username and password, and generating a token upon successful authentication. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/security/first-steps.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, FastAPI +from fastapi.security import OAuth2PasswordBearer + +app = FastAPI() + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + + +@app.get("/items/") +async def read_items(token: str = Depends(oauth2_scheme)): + return {"token": token} +``` + +---------------------------------------- + +TITLE: Declare List with Type Parameter (Python 3.9+) +DESCRIPTION: Illustrates the modern Python 3.9+ syntax for declaring a list with a specific type parameter, such as a list of strings. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +my_list: list[str] +``` + +---------------------------------------- + +TITLE: Defining a FastAPI dependency class with __init__ parameters +DESCRIPTION: This snippet defines `CommonQueryParams`, a Python class designed to be a FastAPI dependency. FastAPI inspects the `__init__` method's parameters (`q`, `skip`, `limit`) to resolve query parameters, providing type validation and improved editor support over dictionary-based dependencies. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +class CommonQueryParams: + def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100): + self.q = q + self.skip = skip + self.limit = limit +``` + +---------------------------------------- + +TITLE: Creating a FastAPI Instance +DESCRIPTION: This code snippet shows how to create an instance of the FastAPI class, which serves as the main entry point for defining API endpoints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Defining a Required Query Parameter in FastAPI +DESCRIPTION: This code snippet demonstrates how to define a required query parameter named 'needy' of type string in a FastAPI endpoint. If the 'needy' parameter is not provided in the request, FastAPI will return an error. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/tr/docs/tutorial/query-params.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +@app.get("/items/{item_id}") +async def read_items(item_id: str, needy: str): + return {"item_id": item_id, "needy": needy} +``` + +---------------------------------------- + +TITLE: Pydantic Models for User Data +DESCRIPTION: Defines Pydantic models for user input, database representation, and output, including handling password hashing. The UserIn model takes username, password, and email. The UserInDB model includes a hashed_password field. The User model excludes the password field. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/extra-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class UserIn(BaseModel): + username: str + password: str + email: str + full_name: Optional[str] = None + + +class User(BaseModel): + username: str + email: str + full_name: Optional[str] = None + + +class UserInDB(BaseModel): + username: str + email: str + full_name: Optional[str] = None + hashed_password: str +``` + +---------------------------------------- + +TITLE: FastAPI Request Flow with Yield and Exception Handling +DESCRIPTION: Illustrates the sequence of operations in a FastAPI request, showing the roles of the client, exception handler, dependency with yield, path operation, and background tasks. It highlights the points at which exceptions can be raised and the implications for response modification, especially after the response has been sent. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_0 + +LANGUAGE: mermaid +CODE: +``` +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,tasks: Can raise exception for dependency, handled after response is sent + Note over client,operation: Can raise HTTPException and can change the response + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise + dep -->> handler: Raise HTTPException + handler -->> client: HTTP error response + dep -->> dep: Raise other exception + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise HTTPException + dep -->> handler: Auto forward exception + handler -->> client: HTTP error response + operation -->> dep: Raise other exception + dep -->> handler: Auto forward exception + end + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> dep: Raise other exception + end + Note over dep: After yield + opt Handle other exception + dep -->> dep: Handle exception, can't change response. E.g. close DB session. + end +``` + +---------------------------------------- + +TITLE: FastAPI: List Query Parameters with Default Values +DESCRIPTION: Shows how to provide a default list of values for a query parameter when it's not explicitly provided in the URL. If `q` is omitted, it will default to `['foo', 'bar']`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: List[str] = Query(default=["foo", "bar"])): + query_items = {"q": q} + return query_items +``` + +---------------------------------------- + +TITLE: Define a Pydantic Model with a List of Submodels +DESCRIPTION: Shows how to define a Pydantic model field as a list containing instances of another Pydantic sub-model (e.g., `List[Image]`). This allows for complex JSON arrays of structured objects, providing full validation, conversion, and documentation for each item in the list. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-nested-models.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional +from pydantic import BaseModel, HttpUrl + +class Image(BaseModel): + url: HttpUrl + name: str + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: List[str] = [] + images: List[Image] +``` + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": [ + "rock", + "metal", + "bar" + ], + "images": [ + { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + }, + { + "url": "http://example.com/dave.jpg", + "name": "The Baz" + } + ] +} +``` + +---------------------------------------- + +TITLE: JSON Schema Examples in Pydantic Models (v1) +DESCRIPTION: Declares examples for a Pydantic model using the `Config` inner class and `schema_extra` to add to the generated JSON schema. This allows including examples in the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/schema-extra-example.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + + class Config: + schema_extra = { + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 50.2, + "tax": 3.2, + } + ] + } +``` + +---------------------------------------- + +TITLE: Required Query Parameter using Ellipsis +DESCRIPTION: This snippet defines a required query parameter `q` using the `Query` class and the ellipsis (`...`) as the default value. This indicates that the parameter is mandatory and must be provided in the request. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/query-params-str-validations.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: str = Query(..., min_length=3)): + return {"q": q} +``` + +---------------------------------------- + +TITLE: Defining a List Field +DESCRIPTION: Demonstrates defining a list field in a Pydantic model without specifying the type of elements within the list. The `tags` attribute will be converted to a list. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-nested-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list = [] +``` + +---------------------------------------- + +TITLE: Define a Pydantic Model with a Set Field (Set[str]) +DESCRIPTION: Illustrates how to define a Pydantic model field as a `set` of a specific type (e.g., `Set[str]`) using `typing.Set`. This ensures that the field only accepts unique items, automatically handling duplicates upon data conversion and providing accurate documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-nested-models.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Set +``` + +LANGUAGE: Python +CODE: +``` +from typing import Set +from pydantic import BaseModel + +class Item(BaseModel): + name: str + tags: Set[str] +``` + +---------------------------------------- + +TITLE: Install Uvicorn with standard dependencies +DESCRIPTION: Command to install Uvicorn, the ASGI server, with its standard dependencies. Uvicorn is commonly used to run FastAPI applications in both development and production environments. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/index.md#_snippet_3 + +LANGUAGE: Shell +CODE: +``` +pip install "uvicorn[standard]" +``` + +---------------------------------------- + +TITLE: Returning a Dictionary +DESCRIPTION: This snippet demonstrates how to return a dictionary containing item information in FastAPI. It shows how to access item attributes and include them in the response. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/index.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Declare Integer Type for FastAPI Parameter +DESCRIPTION: Example of declaring a simple integer type for a function parameter in FastAPI, demonstrating how standard Python type hints are used for automatic validation and documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/README.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +item_id: int +``` + +---------------------------------------- + +TITLE: FastAPI Request Body Type Hint for List (Python 3.9+) +DESCRIPTION: This Python snippet illustrates the modern Python 3.9+ syntax for type hinting a FastAPI request body as a list of Pydantic models. This concise syntax achieves the same result as `typing.List` for environments running Python 3.9 and newer. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_12 + +LANGUAGE: Python +CODE: +``` +images: list[Image] +``` + +---------------------------------------- + +TITLE: Declare Query Parameters with Pydantic Model in FastAPI +DESCRIPTION: This example demonstrates how to define a Pydantic `BaseModel` to structure and validate query parameters in a FastAPI application. The model is then used with `Annotated` and `Query()` to automatically parse and validate incoming query string data, providing type hints and default values. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-param-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated +from fastapi import FastAPI, Query +from pydantic import BaseModel + +class ItemQuery(BaseModel): + limit: int = 10 + offset: int = 0 + +app = FastAPI() + +@app.get("/items/") +async def read_items(query: Annotated[ItemQuery, Query()]): + return {"limit": query.limit, "offset": query.offset} +``` + +---------------------------------------- + +TITLE: FastAPI Dependency Execution Flow (Pre-0.106.0) +DESCRIPTION: Illustrates the sequence of execution for FastAPI dependencies with `yield` and background tasks *before* version 0.106.0. This diagram shows when exceptions were handled, when responses were sent, and the interaction between client, exception handler, dependency, path operation, and background tasks. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_12 + +LANGUAGE: Mermaid +CODE: +``` +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,tasks: Can raise exception for dependency, handled after response is sent + Note over client,operation: Can raise HTTPException and can change the response + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise + dep -->> handler: Raise HTTPException + handler -->> client: HTTP error response + dep -->> dep: Raise other exception + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise HTTPException + dep -->> handler: Auto forward exception + handler -->> client: HTTP error response + operation -->> dep: Raise other exception + dep -->> handler: Auto forward exception + end + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> dep: Raise other exception + end + Note over dep: After yield + opt Handle other exception + dep -->> dep: Handle exception, can't change response. E.g. close DB session. + end +``` + +---------------------------------------- + +TITLE: Using HttpUrl for Validation +DESCRIPTION: Demonstrates using the `HttpUrl` type from Pydantic for validating that a string is a valid URL. The `url` attribute in the `Image` model is defined as an `HttpUrl`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-nested-models.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel, HttpUrl + + +class Image(BaseModel): + url: HttpUrl + name: str + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: set[str] = set() + image: Optional[Image] = None +``` + +---------------------------------------- + +TITLE: Initializing FastAPI with API Metadata - Python +DESCRIPTION: Initializes a FastAPI application with metadata such as title, summary, description, version, terms of service, contact information, and license information. The description field supports Markdown formatting. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/metadata.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI( + title="My Super Project", + summary="Very nice project with very nice code.", + description="Very nice project with very nice code.", + version="0.1.0", + terms_of_service="http://example.com/terms/", + contact={ + "name": "Deadpoolio the Amazing", + "url": "http://example.com/contact/", + "email": "dp@example.com", + }, + license_info={ + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html", + }, +) + + +@app.get("/items/{item_id}") +async def read_items(item_id: str): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Singular Values in Body using Body() +DESCRIPTION: Shows how to instruct FastAPI to treat a singular value as part of the request body using the Body parameter. This is useful when you want to include a simple value alongside other body parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-multiple-params.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item, + user: User, + importance: int = Body(default=None), + q: Union[str, None] = None +): + results = {"item_id": item_id, "item": item, "user": user, "importance": importance} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Returning a Dictionary with Item Name and ID +DESCRIPTION: This snippet shows how to return a dictionary containing the item name and ID. The example is intended to be modified to return the item price instead of the item name. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/index.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Import Pydantic's Field for Model Validation +DESCRIPTION: This snippet demonstrates how to import the `Field` function directly from the `pydantic` library. `Field` is essential for defining advanced validation rules and metadata for attributes within Pydantic models, serving a similar purpose to FastAPI's `Query`, `Path`, and `Body` for request parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-fields.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import Field +``` + +---------------------------------------- + +TITLE: Relative Imports in Python Modules +DESCRIPTION: Illustrates different levels of relative imports in Python, showing how `.` and `..` are used to navigate the package structure. This is crucial for importing modules or functions from sibling or parent directories within a larger application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/bigger-applications.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from .dependencies import get_token_header +``` + +LANGUAGE: Python +CODE: +``` +from ..dependencies import get_token_header +``` + +LANGUAGE: Python +CODE: +``` +from ...dependencies import get_token_header +``` + +---------------------------------------- + +TITLE: Declaring a List of Strings (Python 3.8+) +DESCRIPTION: This snippet demonstrates how to declare a variable as a list of strings using the `List` type hint from the `typing` module in Python 3.8. It imports `List` from `typing` and uses `List[str]` to specify that the variable `items` is a list where each element is a string. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from typing import List + +items: List[str] = ["foo", "bar"] +``` + +---------------------------------------- + +TITLE: Instantiating Pydantic Model with Data +DESCRIPTION: This code demonstrates how to instantiate a Pydantic model, `User`, with data. It shows two methods: direct instantiation with keyword arguments and instantiation using dictionary unpacking (`**second_user_data`). The latter is useful when data is already in a dictionary format. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fa/docs/features.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +---------------------------------------- + +TITLE: Enum for Predefined Path Parameter Values +DESCRIPTION: This example demonstrates how to use Python's `Enum` to define a set of valid values for a path parameter. It imports `Enum` and creates a subclass that inherits from `str` and `Enum`. This allows the API documentation to recognize the values as strings and display them correctly. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/path-params.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +from enum import Enum + +from fastapi import FastAPI + + +class ModelName(str, Enum): + alexnet = "alexnet" + resnet = "resnet" + lenet = "lenet" + + +app = FastAPI() + + +@app.get("/models/{model_name}") +async def get_model(model_name: ModelName): + if model_name is ModelName.alexnet: + return {"model_name": model_name, "message": "Deep Learning FTW!"} + + if model_name.value == "lenet": + return {"model_name": model_name, "message": "LeCNN all the images"} + + return {"model_name": model_name, "message": "Have some residuals"} +``` + +---------------------------------------- + +TITLE: Example JSON Output with response_model_exclude_unset +DESCRIPTION: Provides a JSON response example when `response_model_exclude_unset=True` is applied. It shows that only fields explicitly provided in the input data (e.g., `name` and `price`) are included, while fields with default values that were not explicitly set are omitted. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/response-model.md#_snippet_11 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "price": 50.2 +} +``` + +---------------------------------------- + +TITLE: Optional Type Hinting +DESCRIPTION: Demonstrates how to use `Optional` from the `typing` module to indicate that a variable can be either a string or `None`. This helps editors detect potential errors where a value might be assumed to always be a string. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_13 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +name: Optional[str] = 'Foo' +``` + +---------------------------------------- + +TITLE: Enum Definition - FastAPI (Python) +DESCRIPTION: This code snippet shows how to define an Enum in Python using the `Enum` class from the `enum` module. The Enum inherits from both `str` and `Enum` to ensure that the values are strings and that the documentation can correctly display the Enum. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/path-params.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from enum import Enum + + +class ModelName(str, Enum): + alexnet = "alexnet" + resnet = "resnet" + lenet = "lenet" +``` + +---------------------------------------- + +TITLE: Create Instance of Parameterized FastAPI Dependency +DESCRIPTION: Demonstrates how to instantiate the `FixedContentQueryChecker` class, passing the desired `fixed_content` value. This instance can then be used directly with FastAPI's `Depends()` function, effectively parameterizing the dependency. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/advanced-dependencies.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +# Assuming FixedContentQueryChecker class is defined +checker = FixedContentQueryChecker("bar") +``` + +---------------------------------------- + +TITLE: FastAPI Endpoint to Read Single Hero by ID +DESCRIPTION: Defines a GET endpoint `/heroes/{hero_id}` to fetch a single hero by their unique ID. It queries the database using `session.get()`. If a hero with the specified ID is not found, it raises an `HTTPException` with a 404 Not Found status. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/sql-databases.md#_snippet_8 + +LANGUAGE: python +CODE: +``` +from fastapi import APIRouter, HTTPException, status +from sqlmodel import Session +from .tutorial001_an_py310 import Hero, SessionDep # Assuming Hero and SessionDep are from the same file + +router = APIRouter() + +@router.get("/heroes/{hero_id}", response_model=Hero) +def read_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Hero not found") + return hero +``` + +---------------------------------------- + +TITLE: Making a GET request with Requests +DESCRIPTION: This snippet demonstrates how to make a GET request to a URL using the Requests library in Python. It shows the simplicity and intuitiveness of the library's API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/alternatives.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +response = requests.get("http://example.com/some/url") +``` + +---------------------------------------- + +TITLE: Type Hinting Example +DESCRIPTION: Demonstrates the use of Python type hints for function parameters. This allows IDEs to provide better autocompletion and error checking. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# Déclare une variable comme étant une str +# et profitez de l'aide de votre IDE dans cette fonction +def main(user_id: str): + return user_id +``` + +---------------------------------------- + +TITLE: Defining an Asynchronous Path Operation Function +DESCRIPTION: Defines an asynchronous path operation function named `root` that returns a dictionary containing a message. This function is decorated with `@app.get("/")`, making it the handler for GET requests to the root path `/`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/tutorial/first-steps.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Define Asynchronous GET Path Operation for Root +DESCRIPTION: This example shows how to define an asynchronous GET endpoint for the root path ('/') using the `@app.get()` decorator. The `async def` function handles incoming requests and returns a dictionary, which FastAPI automatically serializes to JSON. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter Response Examples +DESCRIPTION: Provides examples of JSON responses from a FastAPI application when handling query parameters. This includes a validation error response for a missing required parameter and a successful response showing parsed query parameter values. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params.md#_snippet_6 + +LANGUAGE: APIDOC +CODE: +``` +Error Response for Missing Required Parameter: +```json +{ + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "needy" + ], + "msg": "Field required", + "input": null, + "url": "https://errors.pydantic.dev/2.1/v/missing" + } + ] +} +``` + +Successful Response with Required Parameter: +```json +{ + "item_id": "foo-item", + "needy": "sooooneedy" +} +``` +``` + +---------------------------------------- + +TITLE: Declare Cookie Parameters with Pydantic Models in FastAPI +DESCRIPTION: This code shows how to declare and validate multiple cookie parameters using a Pydantic `BaseModel` in FastAPI. It allows for defining required and optional cookies with type annotations, providing a structured way to access and validate incoming cookie data. This method improves code organization and data integrity for cookie handling. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from typing import Annotated + +from fastapi import Cookie, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Cookies(BaseModel): + session_id: str + fatebook_tracker: str | None = None + googall_tracker: str | None = None + + +@app.get("/items/") +async def read_items(cookies: Annotated[Cookies, Cookie()]): + return cookies +``` + +---------------------------------------- + +TITLE: Define Optional Query Parameter in FastAPI +DESCRIPTION: Illustrates + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/query-params-str-validations.md#_snippet_0 + + + +---------------------------------------- + +TITLE: 구니콘 워커 클래스 import +DESCRIPTION: 구니콘에서 사용할 수 있는 유비콘 워커 클래스를 import하는 방법을 보여줍니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/deployment/server-workers.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +import uvicorn.workers.UvicornWorker +``` + +---------------------------------------- + +TITLE: Serve built documentation for local preview +DESCRIPTION: After successfully building the documentation with the `build-all` command, this command serves the generated `./site/` content locally for preview. It's a simple server intended specifically for previewing translated sites, and not recommended for general development purposes. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/contributing.md#_snippet_11 + +LANGUAGE: console +CODE: +``` +// Use the command "serve" after running "build-all" +$ 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 +``` + +---------------------------------------- + +TITLE: Comparing Requests Client and FastAPI Server GET Endpoints +DESCRIPTION: This snippet illustrates the conceptual similarity between making an HTTP GET request using the `requests` library (as a client) and defining an HTTP GET endpoint in FastAPI (as a server). It highlights how FastAPI's decorator syntax for route definition mirrors the client-side request function, showcasing its intuitive design for API development. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/alternatives.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +response = requests.get("http://example.com/some/url") +``` + +LANGUAGE: Python +CODE: +``` +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Multiple Body and Query Parameters in FastAPI +DESCRIPTION: This example demonstrates how to combine multiple body parameters with query parameters in a FastAPI path operation. It shows how to define a query parameter without explicitly using `Query`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-multiple-params.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +@app.post("/items/") +async def create_item( + item: Item, + user: User, + importance: int = Body(gt=0), + q: str | None = None +): + results = {"item": item, "user": user, "importance": importance} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Declare Required Query Parameters in FastAPI +DESCRIPTION: Explains how to define a required query parameter in FastAPI by simply omitting a default value. If the required parameter `needy` is not provided in the URL, FastAPI will automatically return a validation error, ensuring that critical data is always present in requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_user_item(item_id: str, needy: str): + return {"item_id": item_id, "needy": needy} +``` + +---------------------------------------- + +TITLE: Import FastAPI Class +DESCRIPTION: This code imports the FastAPI class from the fastapi module. This class is essential for creating and configuring the API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: Defining a GET Path Operation Decorator +DESCRIPTION: Defines a path operation using the `@app.get()` decorator, which tells FastAPI that the function below it is responsible for handling requests to the specified path using the GET method. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: This command installs FastAPI along with a set of standard optional dependencies. These dependencies provide additional features and integrations for FastAPI applications. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/index.md#_snippet_1 + +LANGUAGE: Shell +CODE: +``` +pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Define SQLModel Hero Class +DESCRIPTION: Defines the `Hero` SQLModel class, inheriting from `SQLModel` and marked as a table (`table=True`). It includes fields for `id` (primary key, optional), `name` (indexed string), `secret_name` (indexed string), and `age` (optional, indexed integer). This class serves as both a Pydantic model for data validation and a SQLAlchemy model for database interaction. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/sql-databases.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from typing import Optional +from sqlmodel import Field, SQLModel + +class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str = Field(index=True) + secret_name: str = Field(index=True) + age: Optional[int] = Field(default=None, index=True) +``` + +---------------------------------------- + +TITLE: Scalar Values in the Body with FastAPI +DESCRIPTION: Illustrates how to include scalar values in the request body using the Body parameter. This example demonstrates how to explicitly define a scalar value as part of the request body, ensuring FastAPI treats it as such. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body-multiple-params.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI, Body + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +class User(BaseModel): + username: str + full_name: Union[str, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item, + user: User, + importance: int = Body(), + q: Union[str, None] = None, +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + results.update({"item": item, "user": user, "importance": importance}) + return results +``` + +---------------------------------------- + +TITLE: Add Pydantic Model and PUT Endpoint to FastAPI +DESCRIPTION: This Python code demonstrates how to enhance a FastAPI application by defining a Pydantic `BaseModel` for data validation and adding a `PUT` endpoint. The `PUT /items/{item_id}` endpoint accepts an `Item` object as a request body, ensuring structured data input. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/index.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Declare Integer Type for FastAPI Parameter +DESCRIPTION: Example of declaring a simple integer type for a function parameter in FastAPI, demonstrating how standard Python type hints are used for automatic validation and documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +item_id: int +``` + +---------------------------------------- + +TITLE: Defining Pydantic Response Models with Default Values +DESCRIPTION: Demonstrates how to define Pydantic models for FastAPI responses that include fields with default values. These defaults can be `None`, empty lists, or specific literal values, influencing how the data is serialized when not explicitly provided by the source. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/response-model.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: float = 10.5 + tags: List[str] = [] +``` + +---------------------------------------- + +TITLE: Declaring a Variable with Type Hints in Python +DESCRIPTION: This code snippet demonstrates how to declare a variable with a type hint in Python using modern Python syntax. It shows how to define a function that accepts a string as input and returns a string, leveraging editor support for type checking and autocompletion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import List, Dict +from datetime import date + +from pydantic import BaseModel + +# Deklarieren Sie eine Variable als ein `str` +# und bekommen Sie Editor-Unterstützung innerhalb der Funktion +def main(user_id: str): + return user_id + + +# Ein Pydantic-Modell +class User(BaseModel): + id: int + name: str + joined: date +``` + +---------------------------------------- + +TITLE: FastAPI Base Response Class (Response) +DESCRIPTION: Documents the core `Response` class in FastAPI (from Starlette), which all other response types inherit from. It details the essential parameters available for constructing a generic HTTP response, including content, status code, headers, and media type. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/advanced/custom-response.md#_snippet_1 + +LANGUAGE: APIDOC +CODE: +``` +Response: + Description: The base class for all HTTP responses in FastAPI, providing fundamental control over the response. + Parameters: + content: str | bytes - The body of the response. Can be a string for text-based content or bytes for binary data. + status_code: int - The HTTP status code for the response (e.g., 200 for OK, 404 for Not Found). + headers: dict[str, str] - An optional dictionary of HTTP headers to include in the response. + media_type: str - The media type (MIME type) of the response, e.g., "text/html", "application/json", "image/png". + Details: + FastAPI (Starlette) automatically includes the Content-Length header. + It also sets the Content-Type header based on the `media_type` and appends encoding for textual types. +``` + +---------------------------------------- + +TITLE: Define List of Strings Field in Pydantic Model (Python 3.10+) +DESCRIPTION: This snippet demonstrates how to define a Pydantic model field as a list specifically containing string elements using Python 3.10+ syntax. This enforces that incoming data for `tags` will be validated as a list of strings, providing strong type enforcement. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +tags: list[str] +``` + +---------------------------------------- + +TITLE: FastAPI Path Operation Decorators Reference +DESCRIPTION: Comprehensive documentation for FastAPI's path operation decorators, which link HTTP methods (GET, POST, PUT, DELETE, etc.) to specific URL paths and Python functions. These decorators define how the API responds to different types of requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/first-steps.md#_snippet_5 + +LANGUAGE: APIDOC +CODE: +``` +@app.get(path: str, ...) + - Purpose: Defines an API endpoint that handles HTTP GET requests. + - Parameters: + - path (str): The URL path for the endpoint (e.g., "/items/", "/"). + - ... (additional parameters for response models, status codes, etc.) + - Usage: Typically used for retrieving or reading data from the server. + - Example: @app.get("/") + +@app.post(path: str, ...) + - Purpose: Defines an API endpoint that handles HTTP POST requests. + - Parameters: + - path (str): The URL path for the endpoint. + - ... (additional parameters) + - Usage: Typically used for creating new resources or submitting data to the server. + +@app.put(path: str, ...) + - Purpose: Defines an API endpoint that handles HTTP PUT requests. + - Parameters: + - path (str): The URL path for the endpoint. + - ... (additional parameters) + - Usage: Typically used for updating existing resources completely. + +@app.delete(path: str, ...) + - Purpose: Defines an API endpoint that handles HTTP DELETE requests. + - Parameters: + - path (str): The URL path for the endpoint. + - ... (additional parameters) + - Usage: Typically used for removing resources from the server. + +@app.options(path: str, ...) +@app.head(path: str, ...) +@app.patch(path: str, ...) +@app.trace(path: str, ...) + - Purpose: Defines API endpoints for less common HTTP methods. + - Parameters: + - path (str): The URL path for the endpoint. + - ... (additional parameters) + - Usage: Provides flexibility for specific API design patterns or advanced HTTP interactions. +``` + +---------------------------------------- + +TITLE: Define Pydantic User Model for FastAPI Security +DESCRIPTION: Defines a Pydantic `BaseModel` to represent the structure of a user, including optional fields like email, full name, and disabled status. This model is crucial for type hinting, data validation, and serialization within FastAPI's dependency injection system. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/get-current-user.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel +from typing import Optional + +class User(BaseModel): + username: str + email: Optional[str] = None + full_name: Optional[str] = None + disabled: Optional[bool] = None +``` + +---------------------------------------- + +TITLE: Importing FastAPI +DESCRIPTION: This code imports the FastAPI class, which provides the core functionality for building APIs. It is the first step in creating a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: Instantiate Pydantic Models in Python +DESCRIPTION: Illustrates how to create instances of Pydantic models, both by direct argument passing and by unpacking a dictionary of data, showcasing type annotation for the instantiated objects. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/features.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30" +} + +my_second_user: User = User(**second_user_data) +``` + +---------------------------------------- + +TITLE: FastAPI: Declaring a Required Query Parameter +DESCRIPTION: Shows how to make a query parameter mandatory by setting its default value to `...` (Ellipsis) when using `Query`. This ensures the `q` parameter must always be provided in the request, otherwise FastAPI returns an error. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: str = Query(default=..., min_length=3)): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Define Pydantic Item Model with Optional Fields +DESCRIPTION: Defines a Pydantic `Item` model with `name` and `price` as required fields, and `description` and `tax` as optional fields with default `None` values. This model is used to demonstrate how Pydantic v2 handles input and output schemas differently based on default values. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/how-to/separate-openapi-schemas.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None +``` + +---------------------------------------- + +TITLE: FastAPI Dependency Execution Flow Diagram +DESCRIPTION: Illustrates the sequence of execution for FastAPI requests involving clients, exception handlers, dependencies with `yield`, path operations, and background tasks, showing where exceptions can be raised and handled. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_4 + +LANGUAGE: mermaid +CODE: +``` +sequenceDiagram + +participant client as Client +participant handler as Exception handler +participant dep as Dep with yield +participant operation as Path Operation +participant tasks as Background tasks + + Note over client,operation: Can raise exceptions, including HTTPException + client ->> dep: Start request + Note over dep: Run code up to yield + opt raise Exception + dep -->> handler: Raise Exception + handler -->> client: HTTP error response + end + dep ->> operation: Run dependency, e.g. DB session + opt raise + operation -->> dep: Raise Exception (e.g. HTTPException) + opt handle + dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception + end + handler -->> client: HTTP error response + end + + operation ->> client: Return response to client + Note over client,operation: Response is already sent, can't change it anymore + opt Tasks + operation -->> tasks: Send background tasks + end + opt Raise other exception + tasks -->> tasks: Handle exceptions in the background task code + end +``` + +---------------------------------------- + +TITLE: Declaring Tuple and Set Type Hints +DESCRIPTION: This example demonstrates how to use type hints for tuples and sets. For tuples, it shows how to specify types for each element, while for sets, it indicates the type of elements contained within the set, covering syntax for Python 3.6+ and 3.9+. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_6 + +LANGUAGE: Python (Python 3.6+) +CODE: +``` +{!> ../../docs_src/python_types/tutorial007.py!} +``` + +LANGUAGE: Python (Python 3.9+) +CODE: +``` +{!> ../../docs_src/python_types/tutorial007_py39.py!} +``` + +---------------------------------------- + +TITLE: Declaring a Union Type (Python 3.10+) +DESCRIPTION: This snippet demonstrates how to declare a variable that can be either an integer or a string using the union operator `|` in Python 3.10 and later. The type hint `int | str` specifies that the variable `item` can hold either an integer or a string value. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +item: int | str = 123 +``` + +---------------------------------------- + +TITLE: FastAPI Password Hashing and Verification Utilities +DESCRIPTION: Provides a simplified example of password hashing and verification functions (`fake_hash_password` and `fake_verify_password`). These utilities are crucial for securely storing and comparing user passwords without keeping them in plaintext, enhancing the application's security posture. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/simple-oauth2.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +def fake_hash_password(password: str): # pragma: no cover + return "supersecret" + password + +def fake_verify_password(plain_password: str, hashed_password: str): # pragma: no cover + return fake_hash_password(plain_password) == hashed_password +``` + +---------------------------------------- + +TITLE: Instantiating Pydantic Models in Python +DESCRIPTION: This example illustrates two ways to instantiate a Pydantic `User` model. The first method directly passes keyword arguments, while the second uses dictionary unpacking (`**`) to create an instance from a dictionary, showcasing flexible object creation from structured data. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/features.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +---------------------------------------- + +TITLE: Class Definition +DESCRIPTION: Defines a simple `Person` class with a `name` attribute. This class is later used as a type hint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_17 + +LANGUAGE: Python +CODE: +``` +class Person: + name: str +``` + +---------------------------------------- + +TITLE: Creating a FastAPI Instance +DESCRIPTION: This code snippet shows how to create an instance of the FastAPI class. This instance, typically named 'app', serves as the main entry point for defining all API endpoints and functionality. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/tutorial/first-steps.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +app +``` + +---------------------------------------- + +TITLE: Define Request Body with Standard Dataclass +DESCRIPTION: Demonstrates how to use a standard Python `dataclass` to define the structure of a request body in a FastAPI application. FastAPI automatically converts this dataclass using Pydantic for validation and serialization, making it a convenient way to define data models without explicitly using Pydantic's `BaseModel`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/dataclasses.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from dataclasses import dataclass + +app = FastAPI() + +@dataclass +class Item: + name: str + price: float + is_offer: bool = False + +@app.post("/items/") +async def create_item(item: Item): + return item +``` + +---------------------------------------- + +TITLE: Importing Pydantic Field +DESCRIPTION: This snippet demonstrates the correct way to import the `Field` class, which is essential for defining advanced validation and metadata for attributes within Pydantic models. Note that `Field` is imported from `pydantic`, not `fastapi`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/body-fields.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import Field +``` + +---------------------------------------- + +TITLE: Reading Hero Data from the Database +DESCRIPTION: This snippet demonstrates how to read hero data from the database using the select() function. It includes the use of limit and offset for pagination of results. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +statement = select(Hero).offset(offset).limit(limit) +results = session.exec(statement) +``` + +---------------------------------------- + +TITLE: Instantiate Pydantic UserIn Model +DESCRIPTION: Demonstrates how to create an instance of a Pydantic `UserIn` model with sample user data, including username, password, and email, for handling incoming user information. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/extra-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +user_in = UserIn(username="john", password="secret", email="john.doe@example.com") +``` + +---------------------------------------- + +TITLE: Defining a Path Operation Decorator +DESCRIPTION: This code snippet demonstrates how to define a path operation decorator using `@app.get("/")`, which associates a function with a specific path and HTTP method (GET in this case). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/first-steps.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +``` + +---------------------------------------- + +TITLE: Example Expected Callback Response Body +DESCRIPTION: A sample JSON payload that the FastAPI application expects as a response from the external callback URL. This body typically indicates the success or failure of the callback operation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/openapi-callbacks.md#_snippet_5 + +LANGUAGE: JSON +CODE: +``` +{ + "ok": true +} +``` + +---------------------------------------- + +TITLE: BackgroundTasks with Dependency Injection +DESCRIPTION: This code snippet illustrates how to use BackgroundTasks with FastAPI's dependency injection system. BackgroundTasks can be declared at different levels (path operation, dependency, sub-dependency), and FastAPI will manage and merge them. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/background-tasks.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import BackgroundTasks, Depends, FastAPI + +app = FastAPI() + + +def write_log(message: str): + with open("log.txt", mode="a") as f: + f.write(message) + + +def get_query(q: Optional[str] = None): + return q + + +@app.post("/send-notification/{email}") +async def send_notification( + email: str, + background_tasks: BackgroundTasks, + q: str = Depends(get_query), +): + background_tasks.add_task(write_log, f"Sent notification to {email}\n") + if q: + background_tasks.add_task(write_log, f"Query parameter q is: {q}\n") + return {"message": "Notification sent in the background"} +``` + +---------------------------------------- + +TITLE: Multiple Body and Query Parameters +DESCRIPTION: Demonstrates how to declare both body and query parameters in a FastAPI route. By default, singular values are interpreted as query parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-multiple-params.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item( + item_id: str, + item: Item, + user: User, + importance: int = Body(default=None), + q: str | None = None +): + results = {"item_id": item_id, "item": item, "user": user, "importance": importance} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Import Depends from FastAPI +DESCRIPTION: This code snippet shows how to import the `Depends` function directly from the `fastapi` library. `Depends` is a fundamental component for defining and injecting dependencies into path operations and other functions in FastAPI applications. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/dependencies.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter with Generic List Type +DESCRIPTION: Shows how to define a query parameter using the generic `list` type hint (without specifying element type). While functional, FastAPI will not perform type checking on the list's contents nor document the element type in OpenAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Query + +app = FastAPI() + +@app.get("/items/") +async def read_items(q: list = Query(default=[])): + return {"q": q} +``` + +---------------------------------------- + +TITLE: Defining a GET Path Operation Decorator +DESCRIPTION: Defines a path operation using the `@app.get()` decorator, associating a function with the `/` path and the HTTP GET method. This tells FastAPI that the function below should handle requests to the specified path using the GET method. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/tutorial/first-steps.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Example PATH Variable on Windows +DESCRIPTION: Illustrates a typical `PATH` environment variable string for Windows systems, showing common directories where executables are located, including Python installation paths. Directories are separated by a semicolon. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/environment-variables.md#_snippet_7 + +LANGUAGE: plaintext +CODE: +``` +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +---------------------------------------- + +TITLE: Defining a Route with GET Operation +DESCRIPTION: This code defines a route for the root path ('/') using the GET operation. The @app.get('/') decorator tells FastAPI that the function below should handle requests to this route using the GET method. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/first-steps.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +@app.get("/") +async def read_root(): + return {"Hello": "World"} +``` + +---------------------------------------- + +TITLE: Створення метаданих для тегів у FastAPI +DESCRIPTION: Приклад створення метаданих для тегів `users` та `items` і передачі їх у параметр `openapi_tags` у FastAPI. Він показує, як додати описи, включаючи Markdown, для тегів, які використовуються для групування операцій шляхів. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/metadata.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI( + openapi_tags=[ + { + "name": "users", + "description": "Operations with users. The **login** logic is also here.", + }, + { + "name": "items", + "description": "Manage items. So _fancy_ they have their own docs.", + "externalDocs": { + "description": "Items external docs description", + "url": "https://example.com/items-docs", + }, + }, + ] +) + + +@app.get("/users", tags=["users"]) +async def read_users(): + return [{"username": "johndoe"}] + + +@app.get("/items", tags=["items"]) +async def read_items(): + return [{"name": "Foo", "price": 50.2}] +``` + +---------------------------------------- + +TITLE: FastAPI Generated OpenAPI Specification +DESCRIPTION: Illustrates a partial view of the OpenAPI (formerly Swagger) specification automatically generated by FastAPI. This JSON document describes the API's endpoints, data models, and operations, enabling automated documentation and client generation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: JSON +CODE: +``` +{ + "openapi": "3.0.2", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... +``` + +---------------------------------------- + +TITLE: Demonstrate Python Script Execution with Environment Variables +DESCRIPTION: Shows the interactive execution of a Python script (`main.py`) to illustrate how it behaves when an environment variable is not set (using the default value) and when it is set using `export`. It highlights the dynamic nature of reading environment variables from the shell. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/advanced/settings.md#_snippet_3 + +LANGUAGE: console +CODE: +``` +python main.py + +Hello World from Python + +export MY_NAME="Wade Wilson" +python main.py + +Hello Wade Wilson from Python +``` + +---------------------------------------- + +TITLE: Ordering Parameters with Syntax Trick +DESCRIPTION: This code snippet demonstrates a Python syntax trick to order parameters when a query parameter doesn't have a default value or `Query` annotation, while a path parameter uses `Path`. The `*` argument forces subsequent parameters to be keyword arguments. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/path-params-numeric-validations.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items(*, item_id: int = Path(), q: str): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: FastAPI 엔드포인트 정의 및 Pydantic 모델 사용 +DESCRIPTION: FastAPI를 사용하여 API 엔드포인트를 정의하고 Pydantic을 사용하여 요청 본문을 정의하는 방법을 보여줍니다. `Item` 모델은 `name`, `price`, `is_offer` 속성을 포함합니다. 엔드포인트는 `/`, `/items/{item_id}` (GET), `/items/{item_id}` (PUT) 입니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/index.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Define a Nested Pydantic Submodel +DESCRIPTION: Defines a simple Pydantic model `Image` that can be used as a nested component within other models. This allows for structured data within a larger payload. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel + +class Image(BaseModel): + url: str + name: str +``` + +---------------------------------------- + +TITLE: Read Multiple Heroes with Pagination +DESCRIPTION: Implements a FastAPI GET endpoint to retrieve a list of `Hero` objects from the database. It supports optional `offset` and `limit` query parameters, enabling pagination for efficient data retrieval. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/sql-databases.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional +from fastapi import APIRouter, Depends +from sqlmodel import Session, select +from typing import Annotated + +# Assume Hero model and SessionDep are defined elsewhere +# class Hero(SQLModel, table=True): +# id: Optional[int] = Field(default=None, primary_key=True) +# name: str +# secret_name: str +# age: Optional[int] = None +# SessionDep = Annotated[Session, Depends(get_session)] + +router = APIRouter() + +@router.get("/heroes/", response_model=List[Hero]) +def read_heroes( + *, + session: SessionDep, + offset: int = 0, + limit: Optional[int] = None +): + heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() + return heroes +``` + +---------------------------------------- + +TITLE: Defining a GET Path Operation Decorator +DESCRIPTION: Defines a path operation using the `@app.get("/")` decorator, which tells FastAPI that the function below is responsible for handling requests to the root path `/` using the GET method. This decorator links the function to a specific URL endpoint and HTTP method. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Pydantic Settings Class for Dependency Injection +DESCRIPTION: Defines the `Settings` class using Pydantic's `BaseSettings` without instantiating it, preparing it for use with FastAPI's dependency injection system. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/settings.md#_snippet_9 + +LANGUAGE: python +CODE: +``` +from pydantic import BaseSettings + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 +``` + +---------------------------------------- + +TITLE: Define and Use Nested Pydantic Models +DESCRIPTION: Explains how to define a Pydantic model as a sub-model and then use it as a field type within another Pydantic model. This enables the creation of deeply nested JSON structures with full data validation, automatic conversion, and comprehensive documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-nested-models.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel + +class Image(BaseModel): + url: str + name: str +``` + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional +from pydantic import BaseModel + +class Image(BaseModel): + url: str + name: str + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: List[str] = [] + image: Image +``` + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +---------------------------------------- + +TITLE: FastAPI PUT Endpoint for Full Item Replacement +DESCRIPTION: Demonstrates a FastAPI endpoint using the HTTP PUT method to fully replace an existing item. It utilizes `jsonable_encoder` to convert the Pydantic model instance into a JSON-compatible dictionary for storage, ensuring data types like `datetime` are handled correctly. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-updates.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item(item_id: str, item: Item): + update_item_encoded = jsonable_encoder(item) + items[item_id] = update_item_encoded + return update_item_encoded +``` + +---------------------------------------- + +TITLE: Returning Enum Members +DESCRIPTION: This example demonstrates how to return Enum members from a path operation. FastAPI automatically converts the Enum members to their corresponding values (strings in this case) in the response. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/path-params.md#_snippet_8 + +LANGUAGE: python +CODE: +``` +@app.get("/models/{model_name}") +async def get_model(model_name: ModelName): + if model_name == ModelName.alexnet: + return {"model_name": model_name, "message": "Deep Learning FTW!"} + + return {"model_name": model_name, "message": f"Have some residuals? {model_name.value}"} + + +@app.get("/models/{model_name}/data") +async def get_model_data(model_name: ModelName): + if model_name.value == "lenet": + return {"model_name": model_name, "data": 42} + return {"model_name": model_name, "data": 19} +``` + +---------------------------------------- + +TITLE: Declaring OpenAPI Examples with openapi_examples in FastAPI +DESCRIPTION: This code snippet demonstrates how to declare OpenAPI examples using the `openapi_examples` parameter within FastAPI. It showcases the structure of the example dictionary, including keys like `summary`, `description`, and `value`, to provide comprehensive examples for API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/schema-extra-example.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +from typing import Optional + +from fastapi import Body, FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + + +@app.post("/items/") +async def create_item( + item: Item = Body( + openapi_examples={ + "normal": { + "summary": "A normal example", + "description": "A **normal** item works correctly.", + "value": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + }, + }, + "invalid": { + "summary": "Invalid example", + "description": "An item that doesn't pass validation.", + "value": { + "name": "Bar", + "price": "thirty five point four", + }, + }, + }, + ), +): + return item +``` + +---------------------------------------- + +TITLE: FastAPI User Authentication and Error Handling +DESCRIPTION: Illustrates the initial steps of user authentication in FastAPI, specifically retrieving user data from a mock database based on the provided username. It demonstrates how to raise an `HTTPException` with a 400 status code if the username is not found, ensuring proper error responses for incorrect credentials. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/simple-oauth2.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` + user = fake_users_db.get(form_data.username) + if not user: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Incorrect username or password", + ) +``` + +---------------------------------------- + +TITLE: Install FastAPI with standard extras using uv +DESCRIPTION: This command installs the FastAPI framework with the 'standard' extras using uv. The 'standard' extras include commonly used dependencies that enhance FastAPI's functionality. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/virtual-environments.md#_snippet_11 + +LANGUAGE: bash +CODE: +``` +uv pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Error Response for Missing Required Query Parameter +DESCRIPTION: This JSON snippet shows the error response returned by FastAPI when a required query parameter (in this case, 'needy') is missing from the request. The response indicates that the 'needy' field is required. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/query-params.md#_snippet_5 + +LANGUAGE: JSON +CODE: +``` +{ + "detail": [ + { + "loc": [ + "query", + "needy" + ], + "msg": "field required", + "type": "value_error.missing" + } + ] +} +``` + +---------------------------------------- + +TITLE: Reading a Single Hero's Data +DESCRIPTION: This snippet shows how to read the data for a single hero from the database, querying by the hero's ID. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +hero = session.get(Hero, hero_id) +``` + +---------------------------------------- + +TITLE: FastAPI: Setting a Default Query Parameter Value +DESCRIPTION: Illustrates how to provide a default value for a query parameter using `Query`. If `q` is not provided in the URL, it will default to 'fixedquery' and still adhere to `min_length` validation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: str = Query(default="fixedquery", min_length=3)): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Testing FastAPI with relative imports +DESCRIPTION: Shows how to import the FastAPI app from main.py using relative imports in test_main.py. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/testing.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from fastapi.testclient import TestClient + +from .main import app + + +client = TestClient(app) + + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"Hello": "World"} +``` + +---------------------------------------- + +TITLE: `lru_cache` Execution Flow Sequence Diagram +DESCRIPTION: This Mermaid sequence diagram visually illustrates the execution flow of a function decorated with `@lru_cache`. It clearly distinguishes between initial calls that execute the function's code and subsequent calls with identical arguments that retrieve results directly from the cache, highlighting the caching mechanism. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/settings.md#_snippet_16 + +LANGUAGE: Mermaid +CODE: +``` +sequenceDiagram + +participant code as Code +participant function as say_hi() +participant execute as Execute function + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Camila") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 0, .1) + code ->> function: say_hi(name="Rick", salutation="Mr.") + function ->> execute: execute function code + execute ->> code: return the result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Rick") + function ->> code: return stored result + end + + rect rgba(0, 255, 255, .1) + code ->> function: say_hi(name="Camila") + function ->> code: return stored result + end +``` + +---------------------------------------- + +TITLE: Query Parameters with Defaults in FastAPI +DESCRIPTION: This code snippet demonstrates how to define query parameters with default values in a FastAPI endpoint. The skip and limit parameters are defined as integers with default values of 0 and 10 respectively. These parameters are automatically converted to integers and can be accessed within the function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/query-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/") +async def read_items(skip: int = 0, limit: int = 10): + return {"skip": skip, "limit": limit} +``` + +---------------------------------------- + +TITLE: Define optional query parameter with pipe operator type hint (Python 3.10+) +DESCRIPTION: Illustrates the modern Python 3.10+ syntax using the pipe operator (`|`) to define an optional query parameter, allowing it to be either a string or `None`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-multiple-params.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +q: str | None = None +``` + +---------------------------------------- + +TITLE: Update FastAPI app with PUT request body +DESCRIPTION: This code defines a Pydantic model `Item` to represent the request body for a PUT request. It also defines a `PUT` endpoint `/items/{item_id}` that receives an `item_id` and an `Item` object, returning a JSON response. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/index.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Default PATH Environment Variable Examples +DESCRIPTION: Illustrates typical values for the PATH environment variable on Linux/macOS and Windows operating systems, showing how directories are separated by colons (Linux/macOS) or semicolons (Windows). These paths indicate the default locations where the system searches for executable programs. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/environment-variables.md#_snippet_4 + +LANGUAGE: plaintext +CODE: +``` +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +LANGUAGE: plaintext +CODE: +``` +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +---------------------------------------- + +TITLE: Install FastAPI with all optional dependencies +DESCRIPTION: Command to install FastAPI along with all its optional dependencies, including Uvicorn, which is used to run the application. This is recommended for a complete development setup. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/index.md#_snippet_1 + +LANGUAGE: Shell +CODE: +``` +$ pip install "fastapi[all]" +``` + +---------------------------------------- + +TITLE: Defining a Synchronous Path Operation Function +DESCRIPTION: Defines a synchronous path operation function named `root` that returns a dictionary containing a message. This function serves the same purpose as the asynchronous example but is defined without the `async` keyword. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/tutorial/first-steps.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Depends 불러오기 - Python +DESCRIPTION: FastAPI에서 Depends를 임포트하여 의존성을 명시적으로 선언할 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/dependencies/index.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from typing import Optional + +from fastapi import Depends, FastAPI +``` + +---------------------------------------- + +TITLE: OpenAPI Schema Example +DESCRIPTION: This is an example of the OpenAPI schema generated by FastAPI. It includes the OpenAPI version, API information (title and version), and paths with their corresponding operations and responses. This schema can be accessed at /openapi.json. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: JSON +CODE: +``` +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... + +``` + +---------------------------------------- + +TITLE: Pydantic Model Dump with exclude_unset for Partial Updates +DESCRIPTION: Shows how to use Pydantic's `.model_dump(exclude_unset=True)` (or `.dict(exclude_unset=True)` for Pydantic v1) to create a dictionary containing only the fields that were explicitly set in the input model, excluding default values. This is crucial for preparing data for partial updates. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-updates.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +item.model_dump(exclude_unset=True) +``` + +---------------------------------------- + +TITLE: Creating a FastAPI Instance +DESCRIPTION: This code snippet shows how to create an instance of the FastAPI class. The app variable will be the main entry point for creating and interacting with the API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +app = FastAPI() +``` + +---------------------------------------- + +TITLE: FastAPI: Adding Minimum Length Validation +DESCRIPTION: Extends query parameter validation by adding a `min_length` constraint using `Query`. The `q` parameter must now be between 3 and 50 characters long if provided in the request. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items( + q: Optional[str] = Query(default=None, min_length=3, max_length=50) +): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: JSON Schema Examples in Pydantic Models (v2) +DESCRIPTION: Declares examples for a Pydantic model using the `model_config` attribute and `json_schema_extra` to add to the generated JSON schema. This allows including examples in the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/schema-extra-example.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel, ConfigDict + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + model_config = ConfigDict( + json_schema_extra={ + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 50.2, + "tax": 3.2, + } + ] + } + ) +``` + +---------------------------------------- + +TITLE: Define Pydantic Model Attributes with List and Set Types +DESCRIPTION: Demonstrates how to declare Pydantic model attributes as Python lists or sets, including type parameters for elements. This ensures data validation for collections and handles uniqueness for sets. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list +``` + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: List[str] +``` + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list[str] +``` + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: set[str] +``` + +---------------------------------------- + +TITLE: Example of an actual callback implementation +DESCRIPTION: Illustrates a simple Python implementation of an HTTP callback using the `httpx` library. This code sends a POST request to a specified URL with a JSON payload, demonstrating the action your API would take to notify an external service. This is the operational code, distinct from its documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/openapi-callbacks.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +callback_url = "https://example.com/api/v1/invoices/events/" +httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) +``` + +---------------------------------------- + +TITLE: Request Body, Path, and Query Parameters +DESCRIPTION: This code snippet demonstrates how to use request body parameters (Pydantic model), path parameters, and query parameters all in the same FastAPI endpoint. FastAPI automatically infers the source of each parameter based on its type and declaration. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def create_item(item_id: int, item: Item, q: Union[str, None] = None): + results = {"item_id": item_id, **item.dict()} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Import FastAPI Class +DESCRIPTION: This code snippet shows how to import the FastAPI class from the fastapi package. This class is essential for creating and configuring your API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: FastAPI Path Operation with Injected Current User +DESCRIPTION: Shows a concise FastAPI path operation that directly receives the Pydantic `User` object by depending on the `get_current_user` function. This demonstrates how FastAPI's dependency injection simplifies access to authenticated user data within endpoints, allowing for cleaner and more type-safe code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/get-current-user.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, FastAPI +from fastapi.security import OAuth2PasswordBearer +from pydantic import BaseModel +from typing import Optional + +# Assume oauth2_scheme is defined, e.g.: +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +# User model definition (can be imported from a models file) +class User(BaseModel): + username: str + email: Optional[str] = None + full_name: Optional[str] = None + disabled: Optional[bool] = None + +# Placeholder for get_current_user (actual implementation in another snippet) +# In a real application, this would be imported from a dependencies file +async def get_current_user(token: str = Depends(oauth2_scheme)): + # Simplified for this example; actual logic would decode token and return User + if token == "valid_token": + return User(username="testuser", email="test@example.com") + raise HTTPException(status_code=400, detail="Invalid token") + +app = FastAPI() + +@app.get("/users/me/") +async def read_users_me(current_user: User = Depends(get_current_user)): + """ + Returns the current authenticated user's information. + The 'current_user' object is automatically provided by the dependency system. + """ + return current_user +``` + +---------------------------------------- + +TITLE: Defining a GET route in FastAPI +DESCRIPTION: This snippet shows how to define a GET route in FastAPI using the @app.get decorator. It demonstrates the similarity in syntax to the Requests library and highlights FastAPI's simple and intuitive API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/alternatives.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Example .env File for Application Settings +DESCRIPTION: This snippet provides an example of a `.env` file, which is used to store environment variables. These variables can be loaded by applications (e.g., using Pydantic Settings) to configure different aspects of the application without hardcoding values directly in the code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/settings.md#_snippet_11 + +LANGUAGE: Bash +CODE: +``` +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +---------------------------------------- + +TITLE: Importing FastAPI +DESCRIPTION: This code snippet shows how to import the FastAPI class from the fastapi package. This is the first step in creating a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: Define Pydantic Settings Class for FastAPI Configuration +DESCRIPTION: This Python snippet defines a `Settings` class using Pydantic's `BaseSettings`. It declares configuration fields like `admin_email` and `app_name` with default values. This approach focuses on defining the structure of settings without instantiating a global object, making it suitable for dependency injection. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/advanced/settings.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + admin_email: str + app_name: str = "Awesome API" +``` + +---------------------------------------- + +TITLE: Define Optional Query Parameter Type Hint +DESCRIPTION: Illustrates how to define an optional query parameter `q` that can be a string or `None`, with `None` as its default value, using standard Python type hints for different versions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_0 + +LANGUAGE: Python 3.10+ +CODE: +``` +q: str | None = None +``` + +LANGUAGE: Python 3.8+ +CODE: +``` +q: Union[str, None] = None +``` + +---------------------------------------- + +TITLE: Importing List from typing +DESCRIPTION: This code snippet shows how to import the `List` type from the `typing` module in Python versions prior to 3.9. This is necessary for declaring lists with specific element types. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body-nested-models.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import List +``` + +---------------------------------------- + +TITLE: Declare Pydantic v2 Model Examples with model_config +DESCRIPTION: Demonstrates how to add example data to a Pydantic v2 model's JSON Schema using the `model_config` attribute and `json_schema_extra` dictionary. This example data will be reflected in the generated OpenAPI documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/schema-extra-example.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + model_config = { + "json_schema_extra": { + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2 + } + ] + } + } +``` + +---------------------------------------- + +TITLE: Using Dependencies in WebSocket Endpoints +DESCRIPTION: This snippet illustrates how to use dependencies, including `Depends`, `Security`, `Cookie`, `Header`, `Path`, and `Query`, within WebSocket endpoints in FastAPI. It shows how to inject dependencies into the WebSocket route to handle authentication, authorization, and data validation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/advanced/websockets.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.websocket("/items/{item_id}") +async def websocket_endpoint( + *, websocket: WebSocket, item_id: int, q: str | None = None, cookie: str | None = Cookie(None) +): + await websocket.accept() + try: + while True: + data = await websocket.receive_text() + await websocket.send_text( + f"Session cookie or query value was: {cookie}, {q}, and you said: {data}, item_id: {item_id}" + ) + except WebSocketDisconnect: + print("Client disconnected") +``` + +---------------------------------------- + +TITLE: 구니콘과 유비콘 설치 +DESCRIPTION: pip를 사용하여 uvicorn과 gunicorn을 설치합니다. uvicorn[standard]는 좋은 성능을 위한 추가 패키지입니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/deployment/server-workers.md#_snippet_0 + +LANGUAGE: bash +CODE: +``` +pip install "uvicorn[standard]" gunicorn +``` + +---------------------------------------- + +TITLE: FastAPI: Applying Regular Expression Validation +DESCRIPTION: Demonstrates how to enforce a specific pattern for a query parameter using the `regex` argument in `Query`. The `q` parameter must exactly match 'fixedquery' if provided, otherwise a validation error occurs. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items( + q: Optional[str] = Query( + default=None, min_length=3, max_length=50, regex="^fixedquery$" + ) +): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Define FastAPI path operation with singular value in body +DESCRIPTION: Illustrates how to include a singular value (e.g., `importance`) directly in the request body alongside Pydantic models, by explicitly using `fastapi.Body()` for that parameter. This ensures it's parsed from the body, not as a query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-multiple-params.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union +from fastapi import FastAPI, Body +from pydantic import BaseModel + +app = FastAPI() + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + +class User(BaseModel): + username: str + full_name: Union[str, None] = None + +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item, + user: User, + importance: int = Body(gt=0) +): + results = {"item_id": item_id, "item": item.dict(), "user": user.dict(), "importance": importance} + return results +``` + +---------------------------------------- + +TITLE: Import `List` for Type Hinting (Python < 3.9) +DESCRIPTION: For Python versions prior to 3.9, the `List` type must be explicitly imported from the standard `typing` module. This is a necessary step to correctly annotate lists with specific element types, ensuring compatibility with older environments. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import List +``` + +---------------------------------------- + +TITLE: Create New Hero with FastAPI and SQLModel +DESCRIPTION: Defines a FastAPI POST endpoint to create a new `Hero` entry in the database. It utilizes the `SessionDep` dependency to manage the database session, adds the new hero, commits the transaction, refreshes the object to get its database-generated ID, and returns the created hero. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/sql-databases.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from fastapi import APIRouter, Depends +from sqlmodel import Session +from typing import Annotated + +# Assume Hero model and SessionDep are defined elsewhere +# class Hero(SQLModel, table=True): +# id: Optional[int] = Field(default=None, primary_key=True) +# name: str +# secret_name: str +# age: Optional[int] = None +# SessionDep = Annotated[Session, Depends(get_session)] + +router = APIRouter() + +@router.post("/heroes/", response_model=Hero) +def create_hero(*, session: SessionDep, hero: Hero): + session.add(hero) + session.commit() + session.refresh(hero) + return hero +``` + +---------------------------------------- + +TITLE: OAuth2 Token Endpoint API Documentation +DESCRIPTION: Defines the API endpoint for obtaining an access token using the OAuth2 password flow. It expects `username` and `password` in the request body and returns an `access_token` and `token_type`. This endpoint is crucial for user authentication. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/security/simple-oauth2.md#_snippet_2 + +LANGUAGE: APIDOC +CODE: +``` +Method: POST +Endpoint: /token + +Request Body (Form Data): + username (string, required): The user's username. + password (string, required): The user's password. + scope (string, optional): Space-separated string of requested permissions (e.g., "users:read users:write"). + grant_type (string, optional, default: "password"): OAuth2 grant type. `OAuth2PasswordRequestForm` does not enforce this, but `OAuth2PasswordRequestFormStrict` does. + client_id (string, optional): Client identifier. + client_secret (string, optional): Client secret. + +Responses: + 200 OK: + access_token (string): The generated access token. + token_type (string): The type of token, typically "bearer". + 401 Unauthorized: + detail (string): "Incorrect username or password" or "Invalid authentication credentials". + WWW-Authenticate (header): "Bearer" + +Dependencies: OAuth2PasswordRequestForm (FastAPI dependency for parsing form data). +Notes: Password hashing should be used for security. The `access_token` in this example is simplified (just the username). +``` + +---------------------------------------- + +TITLE: List with Type Parameters as Field +DESCRIPTION: Demonstrates how to declare a list with a specific type parameter (e.g., a list of strings) in a Pydantic model. This allows for more specific type validation of the list elements. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-nested-models.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: List[str] = [] +``` + +---------------------------------------- + +TITLE: Using Depends in WebSocket endpoint +DESCRIPTION: Demonstrates how to use Depends, Security, Cookie, Header, Path and Query in a WebSocket endpoint. It shows how to inject dependencies into a WebSocket endpoint using FastAPI's dependency injection system. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/advanced/websockets.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import Cookie, Depends, FastAPI, Header, WebSocket, WebSocketException + +app = FastAPI() + + +async def get_cookie_or_token( + websocket: WebSocket, cookie: Optional[str] = Cookie(None), token: Optional[str] = None +): + if cookie is None and token is None: + raise WebSocketException(code=1008, reason="No cookies or token received") + if cookie: + return cookie + return token + + +@app.websocket("/ws/{client_id}") +async def websocket_endpoint( + websocket: WebSocket, + client_id: int, + q: Optional[str] = None, + cookie_or_token: str = Depends(get_cookie_or_token), + last_connection: Optional[str] = Header(None), +): + await websocket.accept() + while True: + try: + data = await websocket.receive_text() + await websocket.send_text( + f"Session cookie or query token value is: {cookie_or_token}" + ) + await websocket.send_text( + f"Message text was: {data}, client_id={client_id}, q={q}" + ) + except WebSocketException: + break +``` + +---------------------------------------- + +TITLE: Defining a Sub-Model +DESCRIPTION: Defines a Pydantic sub-model named `Image` with `url` and `name` attributes. This model can be used as a type for other model attributes, enabling nested data structures. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-nested-models.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Image(BaseModel): + url: str + name: str +``` + +---------------------------------------- + +TITLE: PATH Environment Variable Examples +DESCRIPTION: Provides examples of the `PATH` environment variable's structure on Linux/macOS and Windows. It explains how the OS uses this variable, which contains a list of directories, to locate executable programs when a command is entered in the terminal. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/environment-variables.md#_snippet_4 + +LANGUAGE: plaintext +CODE: +``` +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +LANGUAGE: plaintext +CODE: +``` +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +---------------------------------------- + +TITLE: Returning Content from Path Operation +DESCRIPTION: This code snippet demonstrates how to return content from a path operation function. You can return a dict, list, or single values like str or int. FastAPI automatically converts these to JSON. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/first-steps.md#_snippet_6 + +LANGUAGE: python +CODE: +``` +return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Declare List with Type Parameter (Python < 3.9) +DESCRIPTION: Shows the syntax for declaring a list with a specific type parameter in Python versions before 3.9, requiring the `List` type from the `typing` module. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import List + +my_list: List[str] +``` + +---------------------------------------- + +TITLE: Declare FastAPI Dependency with Simplified Type Hint +DESCRIPTION: Shows alternative ways to declare a FastAPI dependency where the explicit type hint for the parameter is omitted or generalized (e.g., `Any`). While functional, this approach reduces editor assistance for type checking and completion, as the type is inferred solely from the `Depends` function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +commons: Annotated[Any, Depends(CommonQueryParams)] +``` + +LANGUAGE: Python +CODE: +``` +commons = Depends(CommonQueryParams) +``` + +---------------------------------------- + +TITLE: Importing FastAPI +DESCRIPTION: This code snippet shows how to import the FastAPI class from the fastapi package. This is the first step in creating a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/vi/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: Example FastAPI Application File Structure +DESCRIPTION: Illustrates a typical directory and file organization for a larger FastAPI application, highlighting Python package structure with `__init__.py` files and submodules. This setup allows for better organization and import management. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/bigger-applications.md#_snippet_0 + +LANGUAGE: text +CODE: +``` +.\n├── app # "app" is a Python package\n│   ├── __init__.py # this file makes "app" a "Python package"\n│   ├── main.py # "main" module, e.g. import app.main\n│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies\n│   └── routers # "routers" is a "Python subpackage"\n│   │ ├── __init__.py # makes "routers" a "Python subpackage"\n│   │ ├── items.py # "items" submodule, e.g. import app.routers.items\n│   │ └── users.py # "users" submodule, e.g. import app.routers.users\n│   └── internal # "internal" is a "Python subpackage"\n│   ├── __init__.py # makes "internal" a "Python subpackage"\n│   └── admin.py # "admin" submodule, e.g. import app.internal.admin +``` + +---------------------------------------- + +TITLE: Install PassLib with Bcrypt for Password Hashing +DESCRIPTION: Command to install the `passlib` library along with its `bcrypt` dependency using pip. `passlib` is a comprehensive password hashing framework for Python, and Bcrypt is the recommended secure hashing algorithm for storing user passwords. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/oauth2-jwt.md#_snippet_2 + +LANGUAGE: console +CODE: +``` +pip install "passlib[bcrypt]" +``` + +---------------------------------------- + +TITLE: Declaring Pydantic Model Examples +DESCRIPTION: Demonstrates how to embed example data directly within Pydantic models for JSON Schema generation. Includes examples for both Pydantic v1 (using `Config.schema_extra`) and Pydantic v2 (using `model_config['json_schema_extra']`). These examples are added to the model's JSON Schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/schema-extra-example.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Literal +from pydantic import BaseModel, Field + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + tags: list[str] = [] + status: Literal["active", "inactive"] = "active" + + model_config = { + "json_schema_extra": { + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ] + } + } +``` + +LANGUAGE: Python +CODE: +``` +from typing import Literal +from pydantic import BaseModel, Field + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + tags: list[str] = [] + status: Literal["active", "inactive"] = "active" + + class Config: + schema_extra = { + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ] + } +``` + +---------------------------------------- + +TITLE: Declare Pydantic v1 Model Examples with Config Class +DESCRIPTION: Shows how to include example data in a Pydantic v1 model's JSON Schema by defining an internal `Config` class and setting its `schema_extra` attribute. This example data will be used in the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/schema-extra-example.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + class Config: + schema_extra = { + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2 + } + ] + } +``` + +---------------------------------------- + +TITLE: Defining Custom Dependencies in FastAPI +DESCRIPTION: This snippet illustrates how to define a reusable dependency function in a separate file (e.g., `app/dependencies.py`). This specific dependency checks for an `X-Token` header and raises an `HTTPException` if the token is invalid, providing a modular way to enforce authentication or other requirements. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/bigger-applications.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from fastapi import Header, HTTPException + +async def get_token_header(x_token: str = Header()): + if x_token != "fake-super-secret-token": + raise HTTPException(status_code=400, detail="X-Token header invalid") +``` + +---------------------------------------- + +TITLE: Install PassLib with Bcrypt for Password Hashing +DESCRIPTION: Command to install the `passlib` library along with its `bcrypt` extension, providing robust password hashing capabilities for Python applications. `passlib` supports various secure hashing algorithms, with Bcrypt being the recommended choice for strong password security. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/security/oauth2-jwt.md#_snippet_2 + +LANGUAGE: console +CODE: +``` +$ pip install "passlib[bcrypt]" +``` + +---------------------------------------- + +TITLE: Define FastAPI GET Path Operation Function +DESCRIPTION: This snippet demonstrates how to define a path operation function for a GET request to the root path ('/'). It shows both asynchronous (`async def`) and synchronous (`def`) function definitions, which FastAPI calls when a request matches the path and operation. The choice between async and sync depends on whether the function performs I/O-bound operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/first-steps.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +async def root(): +``` + +LANGUAGE: Python +CODE: +``` +def root(): +``` + +---------------------------------------- + +TITLE: Set Field Declaration +DESCRIPTION: This code snippet demonstrates how to declare a set field in a Pydantic model. The `tags` attribute is defined as a `set` of strings, ensuring that the elements are unique. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body-nested-models.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: set[str] = set() +``` + +---------------------------------------- + +TITLE: Function with Type Hints +DESCRIPTION: This example shows how to add type hints to function parameters. It demonstrates how type hints enable autocompletion in code editors. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +def get_full_name(first_name: str, last_name: str): + full_name = first_name.title() + " " + last_name.title() + return full_name + +print(get_full_name("john", "doe")) +``` + +---------------------------------------- + +TITLE: Deeply Nested Pydantic Models +DESCRIPTION: This code demonstrates deeply nested Pydantic models. The `Image` model is nested within the `Item` model, and the `Item` model is nested within the `Offer` model. This allows for complex data structures to be validated and serialized. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-nested-models.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional + +from pydantic import BaseModel + + +class Image(BaseModel): + url: str + name: Optional[str] = None + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: List[str] = [] + images: Optional[List[Image]] = None + + +class Offer(BaseModel): + name: str + items: List[Item] +``` + +---------------------------------------- + +TITLE: JSON Response Example +DESCRIPTION: This JSON snippet shows the expected response from the `/users/me/` endpoint after successful authentication. It includes user details such as username, email, full name, and disabled status. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/security/oauth2-jwt.md#_snippet_4 + +LANGUAGE: JSON +CODE: +``` +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + +---------------------------------------- + +TITLE: Import File and Form from FastAPI +DESCRIPTION: To declare parameters for file uploads and form fields in FastAPI path operations, you need to import the `File` and `Form` classes from the `fastapi` module. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/request-forms-and-files.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import File, Form +``` + +---------------------------------------- + +TITLE: Example FastAPI API Response +DESCRIPTION: An example JSON response from the FastAPI application, demonstrating the structure of data returned for an item query. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/deployment/docker.md#_snippet_4 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Example Python Data with Explicitly Set Default Values +DESCRIPTION: Shows a Python dictionary where fields are explicitly set to their default values (e.g., `description: None`, `tax: 10.5`, `tags: []`). FastAPI and Pydantic consider these fields 'set', meaning they will be included in the response even when `response_model_exclude_unset=True` is active. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/response-model.md#_snippet_13 + + + +---------------------------------------- + +TITLE: Declare Typed List (Python < 3.9 Syntax) +DESCRIPTION: For Python versions older than 3.9, this snippet shows how to declare a list with a specific element type using `List` imported from the `typing` module. This ensures type consistency for list elements in older Python environments. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import List + +my_list: List[str] +``` + +---------------------------------------- + +TITLE: Accessing Pydantic Settings in a FastAPI Application +DESCRIPTION: Demonstrates how to access configuration values from a Pydantic `BaseSettings` object within a FastAPI application's path operation function after instantiating the settings. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/settings.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +# Assuming 'settings' object is already instantiated from Pydantic BaseSettings +# and 'app' is a FastAPI instance. + +@app.get("/info") +async def info(): + return { + "app_name": settings.app_name, + "admin_email": settings.admin_email, + "items_per_user": settings.items_per_user, + } +``` + +---------------------------------------- + +TITLE: Defining a Synchronous Function with `def` +DESCRIPTION: This example shows a standard synchronous function definition using the `def` keyword. Unlike `async def` functions, synchronous functions execute sequentially and block the current thread until their completion, which can lead to performance bottlenecks in I/O-bound scenarios within concurrent applications. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/async.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +def get_sequential_burgers(number: int): + # This is not asynchronous + # Do some sequential stuff to create the burgers + return burgers +``` + +---------------------------------------- + +TITLE: Creating Hero with HeroCreate and Returning HeroPublic +DESCRIPTION: Demonstrates how to create a Hero using the `HeroCreate` model and return the results using the `HeroPublic` model. The `response_model` parameter in the FastAPI route is used to specify the model for validating and serializing the response data. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_14 + +LANGUAGE: Python +CODE: +``` +@router.post("/", response_model=HeroPublic) +async def create_hero(hero: HeroCreate): +``` + +---------------------------------------- + +TITLE: FastAPI CLI Commands: dev and run +DESCRIPTION: Documents the primary FastAPI CLI commands, `fastapi dev` and `fastapi run`, detailing their purpose, default behaviors, and appropriate use cases. It highlights differences in auto-reload, listening IP addresses, and considerations for development versus production environments, noting that both internally use Uvicorn. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/fastapi-cli.md#_snippet_1 + +LANGUAGE: APIDOC +CODE: +``` +FastAPI CLI Commands: + +fastapi dev [path_to_app] + - Description: Starts the FastAPI application in development mode. + - Parameters: + - path_to_app (string, optional): Path to the Python file containing the FastAPI app (e.g., main.py). The CLI automatically detects the 'app' instance. + - Behavior: + - Auto-reload: Enabled by default. Automatically reloads the server on code changes. (Note: Resource-intensive, for development only). + - Listening IP: 127.0.0.1 (localhost). + - Internal: Uses Uvicorn. + +fastapi run [path_to_app] + - Description: Starts the FastAPI application in production mode. + - Parameters: + - path_to_app (string, optional): Path to the Python file containing the FastAPI app (e.g., main.py). The CLI automatically detects the 'app' instance. + - Behavior: + - Auto-reload: Disabled by default. + - Listening IP: 0.0.0.0 (all available IP addresses), making it publicly accessible. + - Production Considerations: Typically used with a "termination proxy" handling HTTPS in production environments. + - Internal: Uses Uvicorn. +``` + +---------------------------------------- + +TITLE: Updating Hero Data with HeroUpdate +DESCRIPTION: This snippet demonstrates how to update hero data using the PATCH method and the HeroUpdate model. It retrieves only the data passed by the client, excluding default values, and updates the hero's information in the database. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_17 + +LANGUAGE: Python +CODE: +``` +@app.patch("/heroes/{hero_id}", response_model=HeroPublic) +async def update_hero( + hero_id: int, hero: HeroUpdate +): + with Session(engine) as session: + hero_db = session.get(Hero, hero_id) + if not hero_db: + raise HTTPException(status_code=404, detail="Hero not found") + hero_data = hero.dict(exclude_unset=True) + hero_db.sqlmodel_update(hero_data) + session.add(hero_db) + session.commit() + session.refresh(hero_db) + return hero_db +``` + +---------------------------------------- + +TITLE: FastAPI: Declare Optional Parameters with None Default and Union +DESCRIPTION: This example shows how to declare optional path, query, cookie, and header parameters in FastAPI. Parameters are made optional by setting their default value to `None` and using `Union[Type, None]` for type hinting, allowing them to be omitted in requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_25 + +LANGUAGE: Python +CODE: +``` +from typing import Union +from fastapi import Cookie, FastAPI, Header, Path, Query + +app = FastAPI() + + +@app.get("/items/{item_id}") +def main( + item_id: int = Path(gt=0), + query: Union[str, None] = Query(default=None, max_length=10), + session: Union[str, None] = Cookie(default=None, min_length=3), + x_trace: Union[str, None] = Header(default=None, title="Tracing header"), +): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Enum Value Retrieval - FastAPI (Python) +DESCRIPTION: This code snippet demonstrates how to retrieve the actual value of an Enum member (a string in this case) using the `.value` attribute. This is useful when you need to use the string representation of the Enum member. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/path-params.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from enum import Enum + +from fastapi import FastAPI + +app = FastAPI() + + +class ModelName(str, Enum): + alexnet = "alexnet" + resnet = "resnet" + lenet = "lenet" + + +@app.get("/models/{model_name}") +async def get_model(model_name: ModelName): + if model_name is ModelName.alexnet: + return {"model_name": model_name, "message": "Deep Learning FTW!"} + + if model_name.value == "lenet": + return {"model_name": model_name, "message": "LeCNN all the images"} + return {"model_name": model_name, "message": "Have some residuals"} +``` + +---------------------------------------- + +TITLE: Defining a GET Path Operation +DESCRIPTION: This code snippet shows how to define a path operation using the @app.get() decorator. The decorator tells FastAPI that the function below it is responsible for handling requests to the specified path ('/') using the GET method. The function returns a dictionary, which FastAPI automatically converts to JSON. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +@app.get("/") +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Returning a Dictionary with Item Name and ID +DESCRIPTION: This code snippet demonstrates how to return a dictionary containing the item name and ID in a FastAPI application. It shows how to access the `name` attribute of an `item` object and include it in the returned dictionary. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/he/docs/index.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: FastAPI Dependency with Yield and Exception Handling +DESCRIPTION: Illustrates how to incorporate `try`, `except`, and `finally` blocks within a `yield`-based dependency. This pattern allows catching exceptions that occur during the dependency's usage (e.g., in a path operation) and ensures that cleanup code in the `finally` block is always executed, regardless of errors. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Generator + +def dependency_with_error_handling() -> Generator: + print("Dependency setup phase") + try: + yield "some_resource" + except Exception as e: + print(f"An exception was caught during dependency usage: {e}") + # Log the error, perform specific rollback, etc. + finally: + print("Cleanup phase (always runs)") +``` + +---------------------------------------- + +TITLE: FastAPI: Handling Multiple Query Parameter Values +DESCRIPTION: Demonstrates how to accept multiple values for a single query parameter by declaring its type as `List[str]` with `Query`. For example, a URL like `?q=foo&q=bar` will result in `q` being `['foo', 'bar']` in the function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from typing import List, Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: Optional[List[str]] = Query(default=None)): + query_items = {"q": q} + return query_items +``` + +---------------------------------------- + +TITLE: Combine Dataclasses with Pydantic and Nested Models +DESCRIPTION: This comprehensive example demonstrates advanced usage of `dataclasses` with FastAPI, including combining standard `dataclasses` with `pydantic.dataclasses` and handling nested data structures. It shows how to use `field` for default factories, define complex response models, and integrate `dataclasses` with both synchronous and asynchronous path operations, leveraging FastAPI's internal Pydantic conversion. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/dataclasses.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from dataclasses import dataclass, field # 1. We import `field` from standard `dataclasses`. + +from fastapi import FastAPI +from pydantic import BaseModel +from pydantic.dataclasses import dataclass as pydantic_dataclass # 2. `pydantic.dataclasses` re-exports `dataclasses`. + +app = FastAPI() + + +@pydantic_dataclass # 3. The `Author` dataclass contains a list of `Item` dataclasses. +class Author: + name: str + items: list["Item"] = field(default_factory=list) + + +@dataclass # 4. The `Author` dataclass is used as a `response_model` parameter. +class Item: + name: str + price: float + description: str | None = None + + +@app.post("/items/", response_model=Item) # 5. You can use the same standard type annotations with dataclasses for incoming data. +async def create_item(item: Item): # In this case, it contains an `Item` dataclass. + return item + + +@app.get("/authors/{author_id}", response_model=Author) # 6. Here we return a dict that contains `items` which are dataclasses. +def get_author(author_id: int): # FastAPI will target `serialize` the data to dataclasses. + return {"name": "John Doe", "items": [{"name": "Book", "price": 10.0}]} + + +@app.get("/authors_pydantic/{author_id}", response_model=Author) # 7. Here, `response_model` uses a type annotation that contains an `Author` dataclass. +def get_author_pydantic(author_id: int): # Again, you can combine dataclasses with standard type annotations. + return {"name": "Jane Doe", "items": [{"name": "Pen", "price": 2.0}]} # 8. Notice that this *path operation function* uses plain `def` instead of `async def`. +``` + +---------------------------------------- + +TITLE: Example JavaScript Code Snippet +DESCRIPTION: Example JavaScript code snippet showing the beginning of a ReDoc standalone JavaScript file. This is used to verify that static files are being served correctly. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/how-to/custom-docs-ui-assets.md#_snippet_7 + +LANGUAGE: JavaScript +CODE: +``` +/*! For license information please see redoc.standalone.js.LICENSE.txt */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")): +``` + +---------------------------------------- + +TITLE: Use a List of Pydantic Submodels as an Attribute +DESCRIPTION: Shows how to define an attribute that expects a list of Pydantic models (e.g., a list of `Image` objects). This is useful for handling multiple related sub-items. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from pydantic import BaseModel + +# Assuming Image model is defined +class Image(BaseModel): + url: str + name: str + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list[str] + images: list[Image] +``` + +---------------------------------------- + +TITLE: Dependencies with yield and HTTPException +DESCRIPTION: This code demonstrates how to use `yield` in dependencies to handle exceptions and perform cleanup after the path operation is executed. It shows how to raise an `HTTPException` within the dependency and how to handle it. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Generator + +from fastapi import Depends, FastAPI, HTTPException, status + + +async def dependency_a() -> str: + yield "dependency_a" + + +async def dependency_b(dependency_a: str = Depends(dependency_a)) -> str: + yield "dependency_b" + + +async def dependency_c(dependency_b: str = Depends(dependency_b)) -> str: + try: + yield "dependency_c" + except Exception: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="From dependency_c") + + +async def dependency_d(dependency_c: str = Depends(dependency_c)) -> str: + yield "dependency_d" + + +app = FastAPI() + + +@app.get("/items/") +async def read_items(dependency_d: str = Depends(dependency_d)) -> dict[str, str]: + return {"dependency_d": dependency_d} +``` + +---------------------------------------- + +TITLE: FastAPI Lifespan Context Manager for Startup/Shutdown +DESCRIPTION: This example demonstrates the recommended way to manage application lifecycle events using FastAPI's `lifespan` context manager. It shows how to initialize resources (e.g., a database connection) before the application starts and clean them up after it shuts down, ensuring proper resource management. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/events.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from contextlib import asynccontextmanager +from typing import Dict + +from fastapi import FastAPI + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Code to run before the app starts + print("Starting up...") + app.state.database = {"foo": "bar"} # Example resource initialization + yield + # Code to run after the app shuts down + print("Shutting down...") + del app.state.database # Example resource cleanup + +app = FastAPI(lifespan=lifespan) +``` + +---------------------------------------- + +TITLE: FastAPI: Caching Settings Dependency with lru_cache +DESCRIPTION: This snippet shows how to apply the `@lru_cache` decorator from `functools` to the `get_settings` dependency. This optimization ensures that the `Settings` object is instantiated only once, improving performance by avoiding redundant file reads or object creations on subsequent calls. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/settings.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from functools import lru_cache # Line 1 +from config import Settings # Assuming Settings is defined in config.py + +@lru_cache # Line 10 +def get_settings(): + return Settings() +``` + +---------------------------------------- + +TITLE: Install openapi-ts for Frontend Client Generation +DESCRIPTION: This command installs the `@hey-api/openapi-ts` package as a development dependency in a frontend project. This tool is used to generate TypeScript clients from an OpenAPI schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/generate-clients.md#_snippet_0 + +LANGUAGE: Console +CODE: +``` +npm install @hey-api/openapi-ts --save-dev +``` + +---------------------------------------- + +TITLE: Define .env file content +DESCRIPTION: Example of a `.env` file defining environment variables like `ADMIN_EMAIL` and `APP_NAME` for application configuration. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/advanced/settings.md#_snippet_0 + +LANGUAGE: bash +CODE: +``` +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +---------------------------------------- + +TITLE: Path and Request Body Parameters in FastAPI +DESCRIPTION: This code snippet illustrates how to declare both path parameters and a request body within the same path operation in FastAPI. FastAPI automatically recognizes that function parameters corresponding to path parameters should be retrieved from the path, while parameters declared as Pydantic models should be retrieved from the request body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def create_item(item_id: int, item: Item): + return {"item_id": item_id, **item.dict()} +``` + +---------------------------------------- + +TITLE: FastAPI Path Parameter Ordering with Query and Path +DESCRIPTION: This snippet illustrates how to handle parameter ordering when mixing required query parameters (without `Query` default) and path parameters (with `Path`). It shows both the traditional Python 3.8 approach where required parameters must come before those with defaults, and the more flexible `Annotated` approach where order is less critical. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params-numeric-validations.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Path, Query + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_items(q: str, item_id: Path(title="The ID of the item to get")): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results +``` + +LANGUAGE: Python +CODE: +``` +from typing import Annotated +from fastapi import FastAPI, Path, Query + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_items( + q: str, + item_id: Annotated[int, Path(title="The ID of the item to get")] +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: FastAPI Dependency with Yield and HTTPException on Cleanup +DESCRIPTION: Demonstrates how to define a FastAPI dependency using `yield` for resource management. It shows that an `HTTPException` can be raised in the `finally` block (cleanup phase) of the dependency, which FastAPI will then handle and return to the client, providing a structured error response even during resource release. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_0 + +LANGUAGE: Python 3.9+ +CODE: +``` +from typing import Annotated +from fastapi import Depends, FastAPI, HTTPException +from contextlib import asynccontextmanager + +app = FastAPI() + +@asynccontextmanager +async def get_resource_with_exception(): + print("Acquiring resource...") + try: + yield "resource_data" + finally: + print("Releasing resource...") + # Simulate an error during resource cleanup + raise HTTPException(status_code=500, detail="Error during resource cleanup in dependency") + +@app.get("/items/") +async def read_items( + resource: Annotated[str, Depends(get_resource_with_exception)] +): + return {"message": f"Using {resource}"} +``` + +LANGUAGE: Python 3.8+ +CODE: +``` +from typing import Annotated +from fastapi import Depends, FastAPI, HTTPException +from contextlib import asynccontextmanager + +app = FastAPI() + +@asynccontextmanager +async def get_resource_with_exception_py38(): + print("Acquiring resource...") + try: + yield "resource_data" + finally: + print("Releasing resource...") + # Simulate an error during resource cleanup + raise HTTPException(status_code=500, detail="Error during resource cleanup in dependency") + +@app.get("/items_py38/") +async def read_items_py38( + resource: Annotated[str, Depends(get_resource_with_exception_py38)] +): + return {"message": f"Using {resource}"} +``` + +LANGUAGE: Python 3.8+ (Non-Annotated) +CODE: +``` +from fastapi import Depends, FastAPI, HTTPException +from contextlib import asynccontextmanager +from typing import Generator + +app = FastAPI() + +@asynccontextmanager +async def get_resource_with_exception_non_annotated() -> Generator[str, None, None]: + print("Acquiring resource...") + try: + yield "resource_data" + finally: + print("Releasing resource...") + # Simulate an error during resource cleanup + raise HTTPException(status_code=500, detail="Error during resource cleanup in dependency") + +@app.get("/items_non_annotated/") +async def read_items_non_annotated( + resource: str = Depends(get_resource_with_exception_non_annotated) +): + return {"message": f"Using {resource}"} +``` + +---------------------------------------- + +TITLE: Example FastAPI OpenAPI JSON Schema +DESCRIPTION: An example of the `openapi.json` file automatically generated by FastAPI, which describes the API's structure, endpoints, and data models using JSON Schema. This schema powers interactive documentation and code generation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/first-steps.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{ + "openapi": "3.1.0", + "info": { + "title": "FastAPI", + "version": "0.1.0" + }, + "paths": { + "/items/": { + "get": { + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + + + +... + } + } + } + } + } + } + } +} +``` + +---------------------------------------- + +TITLE: FastAPI Application and Test File Structure +DESCRIPTION: Illustrates a common project structure for larger FastAPI applications, separating the main application file (`main.py`) from its test file (`test_main.py`) within the same Python package. This setup allows for relative imports of the FastAPI application instance, promoting modularity and maintainability. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/testing.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +def read_main(): + return {"msg": "Hello World"} +``` + +LANGUAGE: Python +CODE: +``` +from fastapi.testclient import TestClient +from .main import app # Import app from the main module + +client = TestClient(app) + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Define FastAPI Path Operations with Tags +DESCRIPTION: This example demonstrates how to organize FastAPI path operations using tags. Tags help in grouping related endpoints, which can then be used by client generators to create structured client code, such as separate service classes for different functional areas like 'items' and 'users'. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/generate-clients.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/", tags=["items"]) +async def read_items(): + return [{"name": "Item 1"}, {"name": "Item 2"}] + +@app.post("/items/", tags=["items"]) +async def create_item(item: dict): + return {"message": "Item created", "item": item} + +@app.get("/users/", tags=["users"]) +async def read_users(): + return [{"name": "User 1"}, {"name": "User 2"}] + +@app.post("/users/", tags=["users"]) +async def create_user(user: dict): + return {"message": "User created", "user": user} +``` + +---------------------------------------- + +TITLE: Define Pydantic Model with Optional Fields +DESCRIPTION: This Pydantic `BaseModel` defines a data structure for an item, including optional fields like `description` and `tax` with default `None` values. These defaults influence how OpenAPI schemas are generated for input and output by FastAPI, leading to distinct schema requirements. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/how-to/separate-openapi-schemas.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None +``` + +---------------------------------------- + +TITLE: Making FastAPI Query Parameters Optional +DESCRIPTION: Illustrates how to define optional query parameters in FastAPI. It compares setting the parameter's default to `None` with using `Query(default=None)`, demonstrating both `typing.Union` and Python 3.10+ `|` syntax for type hints. The `Query` version explicitly declares the parameter as a query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/query-params-str-validations.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +q: Union[str, None] = Query(default=None) +``` + +LANGUAGE: Python +CODE: +``` +q: Union[str, None] = None +``` + +LANGUAGE: Python +CODE: +``` +q: str | None = Query(default=None) +``` + +LANGUAGE: Python +CODE: +``` +q: str | None = None +``` + +---------------------------------------- + +TITLE: JSON: Example FastAPI API response for item query +DESCRIPTION: This JSON object represents a typical response from a FastAPI endpoint, specifically for an item query. It demonstrates the structure of the data returned, including an `item_id` (integer) and an optional `q` parameter (string), which would be part of the query parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/deployment/docker.md#_snippet_14 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: FastAPI User Lookup and Authentication Error Handling +DESCRIPTION: This code demonstrates how to retrieve user data from a (pseudo) database and handle cases where the user is not found. It raises an `HTTPException` with a 400 status code and a specific detail message if the username is incorrect, preventing further processing. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/security/simple-oauth2.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import HTTPException, status + +# ... (within the login_for_access_token function) +# Assuming fake_users_db is a dictionary-like mock database +user_dict = fake_users_db.get(form_data.username) +if not user_dict: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Incorrect username or password", + ) +``` + +---------------------------------------- + +TITLE: Example JSON request body for multiple item and user parameters +DESCRIPTION: Demonstrates the expected JSON structure when a FastAPI path operation accepts multiple Pydantic models (`item` and `user`) as separate body parameters. Each model's data is nested under its corresponding parameter name as a key in the JSON object. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-multiple-params.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + } +} +``` + +---------------------------------------- + +TITLE: Define a synchronous Python function +DESCRIPTION: This example shows a standard synchronous Python function (`def`). It executes sequentially and returns a value without using asynchronous keywords, making it suitable for CPU-bound or blocking operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/async.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +def get_sequential_burgers(number: int): + # Do some sequential stuff to create the burgers + return burgers +``` + +---------------------------------------- + +TITLE: Example Callback Request Body from FastAPI +DESCRIPTION: A sample JSON payload that the FastAPI application sends to the external callback URL. This body contains details about a payment celebration, demonstrating the data sent during a callback. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/openapi-callbacks.md#_snippet_4 + +LANGUAGE: JSON +CODE: +``` +{ + "description": "Payment celebration", + "paid": true +} +``` + +---------------------------------------- + +TITLE: Install Passlib with Bcrypt +DESCRIPTION: Installs the Passlib library with Bcrypt support for handling password hashing. Passlib provides a secure way to store and verify user passwords, protecting them from unauthorized access. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/security/oauth2-jwt.md#_snippet_1 + +LANGUAGE: bash +CODE: +``` +pip install "passlib[bcrypt]" +``` + +---------------------------------------- + +TITLE: Define a dependency with a sub-dependency and cookie +DESCRIPTION: This function acts as a dependency itself, but also depends on `query_extractor` to get a query value. It additionally checks for a `last_query` cookie if the query is not provided, demonstrating nested dependencies and cookie usage in FastAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/sub-dependencies.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from fastapi import Depends, Cookie + +async def query_or_cookie_extractor( + q: str = Depends(query_extractor), + last_query: Optional[str] = Cookie(None), +): + if not q: + return last_query + return q +``` + +---------------------------------------- + +TITLE: Defining a Path Operation Function (Sync) +DESCRIPTION: This code snippet shows how to define a synchronous path operation function that handles requests to a specific path. It uses `def` to define a standard Python function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/first-steps.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +def read_root(): + return {"Hello": "World"} +``` + +---------------------------------------- + +TITLE: Untitled +DESCRIPTION: No description + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/bigger-applications.md#_snippet_10 + + + +---------------------------------------- + +TITLE: Run FastAPI Application with Uvicorn +DESCRIPTION: This command starts the FastAPI application using Uvicorn, a lightning-fast ASGI server. The `--reload` flag enables automatic server restarts on code changes, ideal for development. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: console +CODE: +``` +$ uvicorn main:app --reload + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [28720] +INFO: Started server process [28722] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +---------------------------------------- + +TITLE: Using Sets for Data Models in FastAPI with Pydantic +DESCRIPTION: Demonstrates how to use Python sets in Pydantic models within a FastAPI application. Sets ensure uniqueness of elements, which is useful for data validation and serialization. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/body-nested-models.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: set[str] = set() + + +@app.post("/items/") +async def create_item(item: Item): + return item +``` + +---------------------------------------- + +TITLE: FastAPI Dependency with Yield and Explicit Exception Re-raising (New Behavior) +DESCRIPTION: This code demonstrates the updated and required behavior for FastAPI dependencies using `yield`. After catching an exception within the `try...except` block, it is now mandatory to explicitly re-raise the exception. This change ensures proper error propagation and memory management, aligning the dependency behavior with standard Python exception handling practices. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_10 + +LANGUAGE: Python +CODE: +``` +def my_dep(): + try: + yield + except SomeException: + raise +``` + +---------------------------------------- + +TITLE: Python Import Equivalents for Gunicorn Arguments +DESCRIPTION: These Python import statements conceptually represent how Gunicorn resolves the application module and the worker class specified in its command-line arguments. They illustrate the underlying Python logic for `main:app` and `--worker-class uvicorn.workers.UvicornWorker`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/deployment/server-workers.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from main import app +``` + +LANGUAGE: python +CODE: +``` +import uvicorn.workers.UvicornWorker +``` + +---------------------------------------- + +TITLE: Multiple Body Parameters +DESCRIPTION: Shows how to declare multiple body parameters in a FastAPI endpoint. FastAPI automatically infers that parameters are part of the request body based on their type hints (Pydantic models). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-multiple-params.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +@app.post("/items/") +async def create_item(item: Item, user: User): + return {"item": item, "user": user} +``` + +---------------------------------------- + +TITLE: Path and Request Body Parameters in FastAPI +DESCRIPTION: Shows how to declare both path parameters and a request body using a Pydantic model in a FastAPI endpoint. FastAPI automatically distinguishes between path parameters (extracted from the URL) and request body parameters (parsed from the request body). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/body/tutorial003.py hl[17:18] *} +``` + +---------------------------------------- + +TITLE: Returning an Arbitrary Dictionary in FastAPI +DESCRIPTION: This code demonstrates how to return an arbitrary dictionary as a response, where the types of the keys and values are known, but the specific field names are not. `typing.Dict` is used to specify the response model, indicating that the endpoint will return a dictionary with string keys and integer values. This is useful when the valid field names are not known in advance. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/extra-models.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Dict + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/index/", response_model=Dict[str, int]) +async def index(): + return { + "foo": 3, + "bar": 5 + } +``` + +---------------------------------------- + +TITLE: FastAPI: Original Dependency Usage with Duplication +DESCRIPTION: This example illustrates the traditional method of defining dependencies in FastAPI. It shows how `Depends(get_current_user)` is repeatedly used across multiple path operation functions, leading to noticeable code duplication, especially in larger codebases. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_15 + +LANGUAGE: Python +CODE: +``` +def get_current_user(token: str): + # authenticate user + return User() + + +@app.get("/items/") +def read_items(user: User = Depends(get_current_user)): + ... + + +@app.post("/items/") +def create_item(*, user: User = Depends(get_current_user), item: Item): + ... + + +@app.get("/items/{item_id}") +def read_item(*, user: User = Depends(get_current_user), item_id: int): + ... + + +@app.delete("/items/{item_id}") +def delete_item(*, user: User = Depends(get_current_user), item_id: int): + ... +``` + +---------------------------------------- + +TITLE: Defining Required Query Parameter in FastAPI +DESCRIPTION: This code snippet shows how to define a required query parameter named 'needy' of type string in a FastAPI endpoint. If the 'needy' parameter is not provided in the request, FastAPI will return an error. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/query-params.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_item(item_id: str, needy: str): + return {"item_id": item_id, "needy": needy} +``` + +---------------------------------------- + +TITLE: Declaring Model Attributes with Pydantic Field +DESCRIPTION: This example shows how to use `Field` within a Pydantic `BaseModel` to declare attributes with specific validation rules and metadata. It illustrates setting constraints like `max_length` and `gt` (greater than), and providing a `description` for the field, which contributes to the generated OpenAPI schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/body-fields.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from pydantic import BaseModel, Field + +class Item(BaseModel): + name: str = Field(..., max_length=50) + description: Optional[str] = Field(None, max_length=300, title="Item description") + price: float = Field(..., gt=0, description="Price of the item") +``` + +---------------------------------------- + +TITLE: FastAPI Endpoint to Delete Hero by ID +DESCRIPTION: Defines a DELETE endpoint `/heroes/{hero_id}` to remove a hero from the database based on their ID. It first attempts to retrieve the hero; if found, it deletes the hero from the session and commits the change. If the hero does not exist, it returns an `HTTPException` with a 404 Not Found status. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/sql-databases.md#_snippet_9 + +LANGUAGE: python +CODE: +``` +from fastapi import APIRouter, HTTPException, status +from sqlmodel import Session +from .tutorial001_an_py310 import Hero, SessionDep # Assuming Hero and SessionDep are from the same file + +router = APIRouter() + +@router.delete("/heroes/{hero_id}") +def delete_hero(hero_id: int, session: SessionDep): + hero = session.get(Hero, hero_id) + if not hero: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Hero not found") + session.delete(hero) + session.commit() + return {"ok": True} +``` + +---------------------------------------- + +TITLE: Translating Tip Blocks +DESCRIPTION: This snippet shows the translation of a 'tip' block. The original English text is followed by a vertical bar and then the Spanish translation. This pattern is used consistently for all similar blocks. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/llm-prompt.md#_snippet_1 + +LANGUAGE: Text +CODE: +``` +/// tip | Consejo +``` + +---------------------------------------- + +TITLE: Annotated Type Hint Example (Python 3.9+) +DESCRIPTION: This example shows how to use `Annotated` to add metadata to type hints in Python 3.9 and later. The first type parameter passed to `Annotated` is the actual type, while the rest is metadata. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_22 + +LANGUAGE: Python +CODE: +``` +{!> ../../docs_src/python_types/tutorial013_py39.py!} +``` + +---------------------------------------- + +TITLE: FastAPI Application with GET and PUT Endpoints +DESCRIPTION: This Python code defines a FastAPI application with multiple endpoints. It includes a root GET endpoint, a GET endpoint for items with path and optional query parameters, and a PUT endpoint for updating items. The example demonstrates the use of Pydantic for defining request body models and type hinting for automatic validation and documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/index.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Example .env File for Application Configuration +DESCRIPTION: This snippet provides an example of a `.env` file, a common practice for storing environment-specific configurations. It defines key-value pairs like `ADMIN_EMAIL` and `APP_NAME` that can be loaded by applications, particularly useful for managing sensitive or environment-dependent settings outside of source control. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/advanced/settings.md#_snippet_3 + +LANGUAGE: Bash +CODE: +``` +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +---------------------------------------- + +TITLE: Declare and use a dependency in a FastAPI path operation +DESCRIPTION: This example illustrates how to integrate a defined dependency, `common_parameters`, into a FastAPI path operation. By assigning `Depends(common_parameters)` to a parameter (`commons`), FastAPI automatically calls the dependency function and injects its return value, enabling the path operation to utilize shared logic without direct invocation, promoting modularity and testability. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.get("/items/") +async def read_items(commons: dict = Depends(common_parameters)): + return commons +``` + +---------------------------------------- + +TITLE: Implement FastAPI `get_current_user` Dependency for User Retrieval +DESCRIPTION: Provides the complete implementation of the `get_current_user` dependency. This function takes a raw token, decodes it using a placeholder utility (e.g., `fake_decode_token`), and returns a Pydantic `User` object. It demonstrates how to integrate token validation and user data retrieval into a reusable dependency, raising an `HTTPException` for invalid tokens. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/get-current-user.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, HTTPException +from fastapi.security import OAuth2PasswordBearer +from pydantic import BaseModel +from typing import Optional + +# Assume oauth2_scheme is defined, e.g.: +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +# User model definition (can be imported from a models file) +class User(BaseModel): + username: str + email: Optional[str] = None + full_name: Optional[str] = None + disabled: Optional[bool] = None + +# Fake database and token decoding for demonstration purposes +fake_users_db = { + "john_doe": { + "username": "john_doe", + "email": "john@example.com", + "full_name": "John Doe", + "disabled": False + }, + "jane_doe": { + "username": "jane_doe", + "email": "jane@example.com", + "full_name": "Jane Doe", + "disabled": True + } +} + +def fake_decode_token(token: str): + """ + Placeholder for actual token decoding logic (e.g., JWT verification). + In a real application, this would validate the token and fetch user data from a database. + """ + return fake_users_db.get(token) + +async def get_current_user(token: str = Depends(oauth2_scheme)): + """ + FastAPI dependency to retrieve the current user from a token. + Raises HTTPException if the token is invalid or user not found. + """ + user_data = fake_decode_token(token) + if not user_data: + raise HTTPException(status_code=400, detail="Invalid token") + return User(**user_data) +``` + +---------------------------------------- + +TITLE: Defining Path Operation Decorator with FastAPI +DESCRIPTION: This code snippet shows how to define a path operation decorator using `@app.get("/")` in FastAPI. It tells FastAPI that the function below handles requests to the `/` path using the HTTP GET method. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +``` + +---------------------------------------- + +TITLE: Example JSON request body for an embedded single item +DESCRIPTION: Illustrates the expected JSON structure when a single Pydantic model (`Item`) is embedded under a key (`item`) in the request body using `Body(embed=True)` in FastAPI + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-multiple-params.md#_snippet_11 + + + +---------------------------------------- + +TITLE: Convert Query Parameters to Boolean in FastAPI +DESCRIPTION: Shows how FastAPI automatically converts various string representations (e.g., '1', 'True', 'on', 'yes') from query parameters into a Python boolean type. This example defines a `short` boolean query parameter with a default value, simplifying client input for boolean flags. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_item(item_id: str, short: bool = False): + if short: + return {"item_id": item_id, "description": "This is an amazing item that has a short description."} + return {"item_id": item_id, "description": "This is an amazing item that has a long description."} +``` + +---------------------------------------- + +TITLE: Handling Body, Route, and Query Parameters in FastAPI +DESCRIPTION: This code demonstrates FastAPI's ability to manage request body, route parameters, and query parameters concurrently. It showcases how FastAPI automatically infers the source of each parameter based on its type and declaration. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def create_item(item_id: int, item: Item, q: Union[str, None] = None): + results = {"item_id": item_id, **item.dict()} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: OpenAPI Specification for External Callback Endpoint +DESCRIPTION: Defines the expected structure of an external API endpoint that receives callbacks from the main FastAPI application. This OpenAPI specification details the required POST path operation, its request body schema (for invoice payment notifications), and expected responses, enabling external developers to implement compatible APIs that can receive notifications from your service. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/openapi-callbacks.md#_snippet_1 + +LANGUAGE: APIDOC +CODE: +``` +paths: + /api/v1/invoices/events/: + post: + summary: Receive invoice payment notification callback + description: Endpoint for the main API to send notifications about invoice payment status. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + description: + type: string + description: A description of the payment event, e.g., "Invoice paid". + paid: + type: boolean + description: Indicates whether the invoice was paid (true) or not. + example: + description: Invoice paid + paid: true + responses: + '200': + description: Callback successfully received. +``` + +---------------------------------------- + +TITLE: Annotated Dependency Example +DESCRIPTION: Shows how to define a reusable dependency using `Annotated` and `Depends` in FastAPI. The example defines common query parameters and reuses them in multiple path operations, maintaining type hints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/dependencies/index.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated + +from fastapi import Depends, FastAPI + +app = FastAPI() + + +async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} + + +CommonsDep = Annotated[dict, Depends(common_parameters)] + + +@app.get("/items/") +async def read_items(commons: CommonsDep): + return commons + + +@app.get("/users/") +async def read_users(commons: CommonsDep): + return commons +``` + +---------------------------------------- + +TITLE: Using Pydantic Special Types (HttpUrl) for Validation +DESCRIPTION: Demonstrates how to use Pydantic's built-in special types, such as `HttpUrl`, for advanced data validation. Declaring a field as `HttpUrl` automatically validates the string as a valid URL and documents it accordingly in the OpenAPI schema, enhancing data integrity. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-nested-models.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel, HttpUrl + +class Image(BaseModel): + url: HttpUrl + name: str +``` + +---------------------------------------- + +TITLE: Define Set of Strings Field in Pydantic Model (Python 3.10+) +DESCRIPTION: This example shows how to use a Python `set` for a Pydantic model field, specifically for unique string elements. This automatically handles duplicate entries by converting them into a set of unique items, which is useful for tags or categories. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body-nested-models.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +tags: set[str] +``` + +---------------------------------------- + +TITLE: Using Pydantic Model Attributes in FastAPI +DESCRIPTION: Demonstrates how to access attributes of a Pydantic model directly within a FastAPI function. This allows for easy access to the data validated and parsed by the model. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +{* ../../docs_src/body/tutorial002.py hl[21] *} +``` + +---------------------------------------- + +TITLE: FastAPI Dependency with Yield and Exception Handling +DESCRIPTION: Demonstrates how to use `yield` in FastAPI dependencies to manage resources, including raising `HTTPException` or custom exceptions after `yield`. It shows a dependency that yields a username and a path operation that retrieves an item, validating ownership and raising appropriate exceptions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +from fastapi import Depends, FastAPI, HTTPException +from typing_extensions import Annotated + +app = FastAPI() + + +data = { + "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"}, + "portal-gun": {"description": "Gun to create portals", "owner": "Rick"}, +} + + +class OwnerError(Exception): + pass + + +def get_username(): + try: + yield "Rick" + except OwnerError as e: + raise HTTPException(status_code=400, detail=f"Owner error: {e}") + + +@app.get("/items/{item_id}") +def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): + if item_id not in data: + raise HTTPException(status_code=404, detail="Item not found") + item = data[item_id] + if item["owner"] != username: + raise OwnerError(username) + return item +``` + +---------------------------------------- + +TITLE: FastAPI application file +DESCRIPTION: Defines a simple FastAPI application in main.py. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/testing.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def read_root(): + return {"Hello": "World"} +``` + +---------------------------------------- + +TITLE: Install FastAPI Standard Dependencies Excluding Cloud CLI +DESCRIPTION: Provides the installation command for FastAPI's standard dependencies while specifically omitting the `fastapi-cloud-cli` package, useful for users who do not require cloud deployment features. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_15 + +LANGUAGE: Python +CODE: +``` +pip install "fastapi[standard-no-fastapi-cloud-cli]" +``` + +---------------------------------------- + +TITLE: Environment Variables: Example .env File +DESCRIPTION: This snippet shows a typical `.env` file structure used to define environment variables. Pydantic settings can automatically load values from such files, providing a convenient way to manage configuration outside of code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/settings.md#_snippet_6 + +LANGUAGE: bash +CODE: +``` +ADMIN_EMAIL="deadpool@example.com" +APP_NAME="ChimichangApp" +``` + +---------------------------------------- + +TITLE: Numeric Validation: Float Values, Greater Than and Less Than +DESCRIPTION: This code snippet shows how numeric validations work with float values. It uses `gt` (greater than) and `lt` (less than) to specify that `item_id` must be greater than 0 and less than 1. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/path-params-numeric-validations.md#_snippet_6 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI, Path + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items(item_id: float = Path(gt=0, lt=1)): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Set Type as Field in Pydantic Model +DESCRIPTION: Defines a Pydantic model with a set as a field, ensuring that the elements are unique. The `tags` attribute is declared as a set of strings. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-nested-models.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Optional, Set + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: Set[str] = set() +``` + +---------------------------------------- + +TITLE: Example JSON Response +DESCRIPTION: This JSON response is returned when accessing the /items/{item_id} endpoint with a query parameter. It demonstrates how FastAPI automatically serializes data into JSON format. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/index.md#_snippet_5 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Declare a List with Type Annotation in Python +DESCRIPTION: Demonstrates how to declare a list with type annotations in Python, specifically using the `List` type from the `typing` module for Python versions prior to 3.9. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/body-nested-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import List + +my_list: List[str] +``` + +---------------------------------------- + +TITLE: Obtaining Enum Value +DESCRIPTION: This example shows how to obtain the actual string value of an Enum member using `.value`. This is useful when you need to work with the string representation of the Enum. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/path-params.md#_snippet_7 + +LANGUAGE: python +CODE: +``` +@app.get("/models/{model_name}") +async def get_model(model_name: ModelName): + if model_name == ModelName.alexnet: + return {"model_name": model_name, "message": "Deep Learning FTW!"} + + return {"model_name": model_name, "message": f"Have some residuals? {model_name.value}"} +``` + +---------------------------------------- + +TITLE: Define Deeply Nested Pydantic Models +DESCRIPTION: Illustrates how to create arbitrarily deeply nested Pydantic models, where models contain lists of other models, which in turn contain optional lists of yet other models. This demonstrates FastAPI's capability to handle complex, multi-level data structures. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_10 + +LANGUAGE: Python +CODE: +``` +from typing import Optional +from pydantic import BaseModel, HttpUrl + +class Image(BaseModel): + url: HttpUrl + name: str + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: set[str] + images: Optional[list[Image]] = None + +class Offer(BaseModel): + name: str + description: Optional[str] = None + price: float + items: list[Item] +``` + +---------------------------------------- + +TITLE: Accessing Dependency Values in Exit Code +DESCRIPTION: Shows how to access the values of dependencies in the exit code of other dependencies when using `yield`. `dependency_b` needs the value of `dependency_a` and `dependency_c` needs the value of `dependency_b` to execute their exit code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +async def dependency_a() -> str: + yield "A" + + +async def dependency_b(dep_a: str = Depends(dependency_a)) -> str: + try: + yield f"B {dep_a}" + finally: + print(f"dependency_b got {dep_a=}") + + +async def dependency_c(dep_b: str = Depends(dependency_b)) -> str: + try: + yield f"C {dep_b}" + finally: + print(f"dependency_c got {dep_b=}") +``` + +---------------------------------------- + +TITLE: Accessing Model Attributes in FastAPI +DESCRIPTION: This code snippet demonstrates how to access attributes of a Pydantic model directly within a FastAPI function. It showcases the ease of use and type safety provided by Pydantic models when handling request bodies. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Item): + return item.name +``` + +---------------------------------------- + +TITLE: FastAPI Application for Separate Testing +DESCRIPTION: This code defines a simple FastAPI application with a single GET endpoint. It is designed to be part of a larger project structure where the application logic resides in one file (`main.py`) and tests are written in a separate file, promoting modularity and organization. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/testing.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/") +async def read_main(): + return {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Import UploadFile from FastAPI +DESCRIPTION: Demonstrates the standard way to import the `UploadFile` class directly from the `fastapi` library, which is essential for defining file upload parameters in FastAPI applications. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/uploadfile.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import UploadFile +``` + +---------------------------------------- + +TITLE: Declaring Dictionary Type Hints +DESCRIPTION: This snippet illustrates how to declare type hints for dictionaries, specifying the types for both keys and values. It provides examples for both Python 3.6+ (using `typing.Dict`) and Python 3.9+ (using the built-in `dict` type directly), clarifying the structure of dictionary type annotations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_7 + +LANGUAGE: Python (Python 3.6+) +CODE: +``` +{!> ../../docs_src/python_types/tutorial008.py!} +``` + +LANGUAGE: Python (Python 3.9+) +CODE: +``` +{!> ../../docs_src/python_types/tutorial008_py39.py!} +``` + +---------------------------------------- + +TITLE: Example JSON Data with Values Matching Defaults +DESCRIPTION: Shows a JSON data structure where some values are explicitly set to be identical to their Pydantic model's default values. FastAPI and Pydantic intelligently include these in the response because they were explicitly provided, rather than being derived from the model's defaults. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/response-model.md#_snippet_16 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Baz", + "description": null, + "price": 50.2, + "tax": 10.5, + "tags": [] +} +``` + +---------------------------------------- + +TITLE: Type hinting in functieparameters +DESCRIPTION: Demonstreert het gebruik van type hints in functieparameters in Python. De functie `main` accepteert een string parameter `user_id` en retourneert deze. Type hints zorgen voor editorondersteuning en typecontrole. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# Declareer een variabele als een str +# en krijg editorondersteuning in de functie +def main(user_id: str): + return user_id +``` + +---------------------------------------- + +TITLE: Declaring a Union Type - Python 3.10+ +DESCRIPTION: This snippet declares a variable `item` that can be either an integer or a string using the union operator `|`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_12 + +LANGUAGE: Python +CODE: +``` +item: int | str = 123 +``` + +---------------------------------------- + +TITLE: FastAPI Application with Tagged Endpoints +DESCRIPTION: Demonstrates how to organize FastAPI endpoints using `tags`. Tags allow for better categorization and grouping of related operations in the generated OpenAPI documentation, which can lead to more structured client code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/generate-clients.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI, Body +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +class User(BaseModel): + username: str + email: Union[str, None] = None + + +app = FastAPI() + + +@app.post("/items/", tags=["items"]) +async def create_item(item: Item = Body(..., embed=True)): + return item + + +@app.get("/items/", tags=["items"]) +async def read_items(): + return [{"name": "Foo", "price": 42}] + + +@app.post("/users/", tags=["users"]) +async def create_user(user: User = Body(..., embed=True)): + return user + + +@app.get("/users/", tags=["users"]) +async def read_users(): + return [{"username": "Foo"}, {"username": "Bar"}] +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: An example command to install the FastAPI library along with its standard extra dependencies using `pip`. This illustrates a common package installation process, typically performed within a virtual environment. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/virtual-environments.md#_snippet_4 + +LANGUAGE: Shell +CODE: +``` +pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Hero Public Data Model Definition +DESCRIPTION: Defines the `HeroPublic` model, which is used for returning Hero data to API clients. It includes the same fields as `HeroBase` (name, age) and an `id` field, but excludes the `secret_name` to protect sensitive information. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +class HeroPublic(HeroBase): + id: int +``` + +---------------------------------------- + +TITLE: Import TestClient for FastAPI testing +DESCRIPTION: This snippet demonstrates how to import the `TestClient` class from the `fastapi.testclient` module. This class is crucial for writing unit and integration tests for FastAPI applications, allowing direct interaction with the application's routes and logic. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/testclient.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi.testclient import TestClient +``` + +---------------------------------------- + +TITLE: Declaring a Union Type (Python 3.8+) +DESCRIPTION: This snippet demonstrates how to declare a variable that can be either an integer or a string using the `Union` type from the `typing` module in Python 3.8+. The type hint `Union[int, str]` specifies that the variable `item` can hold either an integer or a string value. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_12 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +item: Union[int, str] = 123 +``` + +---------------------------------------- + +TITLE: FastAPI Application-Level Dependencies +DESCRIPTION: This snippet demonstrates how to set top-level dependencies for a FastAPI application by passing a list of `Depends` objects to the `FastAPI` constructor. This allows applying global authentication or other dependencies to all path operations in the application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_28 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Depends + + +async def some_dependency(): + return + + +app = FastAPI(dependencies=[Depends(some_dependency)]) +``` + +---------------------------------------- + +TITLE: OpenAPI 3.1.0 Webhook Definition for New Subscription Event +DESCRIPTION: This APIDOC entry describes the structure of a webhook definition as generated by FastAPI in the OpenAPI 3.1.0 schema. It details the 'new-subscription' webhook, including its HTTP POST method, the expected request body schema (`NewSubscription`), and the response, providing a comprehensive reference for consumers to implement their webhook receivers. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/openapi-webhooks.md#_snippet_1 + +LANGUAGE: APIDOC +CODE: +``` +{ + "webhooks": { + "new-subscription": { + "post": { + "summary": "This webhook is triggered when a new user subscribes.", + "description": "It receives details about the new subscription.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewSubscription" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Webhook processed successfully" + } + } + } + } + }, + "components": { + "schemas": { + "NewSubscription": { + "title": "NewSubscription", + "required": [ + "email", + "plan", + "amount" + ], + "type": "object", + "properties": { + "email": { + "title": "Email", + "type": "string" + }, + "plan": { + "title": "Plan", + "type": "string" + }, + "amount": { + "title": "Amount", + "type": "number" + } + } + } + } + } +} +``` + +---------------------------------------- + +TITLE: Import Starlette HTTPException with Alias +DESCRIPTION: This Python snippet demonstrates how to import Starlette's `HTTPException` class and assign it an alias (`StarletteHTTPException`). This is crucial when registering exception handlers to differentiate it from FastAPI's `HTTPException` and ensure broader error handling coverage. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/handling-errors.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +from starlette.exceptions import HTTPException as StarletteHTTPException +``` + +---------------------------------------- + +TITLE: Import Header Class from FastAPI +DESCRIPTION: Demonstrates the essential import statement to bring the `Header` class into your FastAPI application, enabling the definition of HTTP header parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/header-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Header + +app = FastAPI() +``` + +---------------------------------------- + +TITLE: FastAPI: Using Aliases for Query Parameters +DESCRIPTION: Explains how to use the `alias` argument in `Query` to map an invalid Python variable name (e.g., `item-query`) from the URL to a valid Python parameter name (`q`). This allows flexible URL parameter naming while maintaining valid Python code. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: Optional[str] = Query(default=None, alias="item-query")): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Example requirements.txt +DESCRIPTION: Shows the format of a requirements.txt file, which lists the packages and their versions required for a project. This file is used with pip install -r requirements.txt. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/virtual-environments.md#_snippet_14 + +LANGUAGE: requirements.txt +CODE: +``` +fastapi[standard]==0.113.0 +pydantic==2.8.0 +``` + +---------------------------------------- + +TITLE: Hero Update Model Definition +DESCRIPTION: Defines the `HeroUpdate` model, used for updating existing Hero data. All fields in this model are optional, allowing clients to update only the fields that need to be changed. It includes fields for `name`, `age`, and `secret_name`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_13 + +LANGUAGE: Python +CODE: +``` +class HeroUpdate(HeroBase): + name: Optional[str] = None + age: Optional[int] = None + secret_name: Optional[str] = None +``` + +---------------------------------------- + +TITLE: Using HttpUrl for URL Validation +DESCRIPTION: Demonstrates how to use Pydantic's `HttpUrl` type for validating that a string is a valid URL. This ensures that the `url` field in the `Image` model contains a valid HTTP URL. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-nested-models.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel, HttpUrl + + +class Image(BaseModel): + url: HttpUrl + name: str + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list[str] = [] + image: Optional[Image] = None +``` + +---------------------------------------- + +TITLE: Defining a GET Path Operation with Decorator in FastAPI +DESCRIPTION: This code snippet shows how to define a GET path operation for the root path ('/') using the `@app.get()` decorator in FastAPI. The function decorated with `@app.get("/")` will handle requests to the `/` path using the GET method. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/first-steps.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +@app.get("/") +``` + +---------------------------------------- + +TITLE: Prepare Optional Query Parameter with Annotated +DESCRIPTION: Shows how to wrap an optional string type hint for parameter `q` with `Annotated`, preparing it for additional metadata like validation rules in FastAPI, for different Python versions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_1 + +LANGUAGE: Python 3.10+ +CODE: +``` +q: Annotated[str | None] = None +``` + +LANGUAGE: Python 3.8+ +CODE: +``` +q: Annotated[Union[str, None]] = None +``` + +---------------------------------------- + +TITLE: Alternative JSON Body without Embedding +DESCRIPTION: This JSON snippet shows the structure of the request body when the `embed` parameter is not used. The `Item` model's data is directly in the body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body-multiple-params.md#_snippet_6 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +---------------------------------------- + +TITLE: Single Values in Request Body +DESCRIPTION: Demonstrates how to use `Body` to explicitly define a single value as part of the request body. This is useful when you need to include simple data types in the request body alongside Pydantic models. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-multiple-params.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.post("/items/{item_id}") +async def update_item( + item_id: int, + item: Item, + user: User, + importance: int = Body(..., gt=0), + q: Union[str, None] = None +): + results = {"item_id": item_id, "item": item, "user": user, "importance": importance} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: 패스워드 해싱 및 검증 유틸리티 함수 생성 (passlib) +DESCRIPTION: passlib에서 필요한 도구를 가져오고, 패스워드를 해싱하고 검증하는 데 사용되는 PassLib "컨텍스트"를 생성합니다. 사용자로부터 받은 패스워드를 해싱하는 유틸리티 함수와 저장된 해시와 일치하는지 검증하는 함수를 생성합니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/security/oauth2-jwt.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def get_password_hash(password): + return pwd_context.hash(password) + +def verify_password(password, hashed_password): + return pwd_context.verify(password, hashed_password) +``` + +---------------------------------------- + +TITLE: Dict with Specific Key and Value Types +DESCRIPTION: This code defines a request body as a dictionary with integer keys and float values. The `weights` parameter is annotated as `dict[int, float]`, indicating that the request body should be a JSON object where the keys are integers and the values are floats. Pydantic automatically converts string keys to integers. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-nested-models.md#_snippet_10 + +LANGUAGE: Python +CODE: +``` +from typing import Dict + +from pydantic import BaseModel + + +async def update_weights(weights: Dict[int, float]): + return weights +``` + +---------------------------------------- + +TITLE: Hero Creation Model Definition +DESCRIPTION: Defines the `HeroCreate` model, used for validating data received from clients when creating a new Hero. It includes fields for `name`, `age`, and `secret_name`, allowing clients to provide the secret name during creation, which is then stored in the database but not returned in API responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_12 + +LANGUAGE: Python +CODE: +``` +class HeroCreate(HeroBase): + secret_name: str +``` + +---------------------------------------- + +TITLE: Declaring a Union Type - Python 3.8+ +DESCRIPTION: This snippet declares a variable `item` that can be either an integer or a string using the `Union` type from the `typing` module. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +item: Union[int, str] = 123 +``` + +---------------------------------------- + +TITLE: Run FastAPI Development Server +DESCRIPTION: This command starts the FastAPI development server, watching for changes in `main.py` and automatically reloading the application. It provides a local URL for accessing the API and its interactive documentation. This mode is suitable for development, while `fastapi run` is recommended for production. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/index.md#_snippet_0 + +LANGUAGE: Shell +CODE: +``` +$ fastapi dev main.py +``` + +---------------------------------------- + +TITLE: FastAPI OpenAPI Component Schemas for Pydantic Models +DESCRIPTION: This API documentation snippet illustrates the 'components/schemas' section of the OpenAPI specification, where Pydantic models are defined as reusable JSON schemas. Models like 'Message', 'Item', 'ValidationError', and 'HTTPValidationError' are detailed with their properties, types, and required fields. These definitions are then referenced from other parts of the OpenAPI document, promoting reusability and consistency. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/additional-responses.md#_snippet_1 + +LANGUAGE: JSON +CODE: +``` +{ + "components": { + "schemas": { + "Message": { + "title": "Message", + "required": [ + "message" + ], + "type": "object", + "properties": { + "message": { + "title": "Message", + "type": "string" + } + } + }, + "Item": { + "title": "Item", + "required": [ + "id", + "value" + ], + "type": "object", + "properties": { + "id": { + "title": "Id", + "type": "string" + }, + "value": { + "title": "Value", + "type": "string" + } + } + }, + "ValidationError": { + "title": "ValidationError", + "required": [ + "loc", + "msg", + "type" + ], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "type": "string" + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + } + } + } +} +``` + +---------------------------------------- + +TITLE: Example JSON request body for a single item +DESCRIPTION: Illustrates the expected JSON structure when a FastAPI path operation expects a single Pydantic model as the request body without any embedding. The model's attributes are directly at the root level of the JSON object. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-multiple-params.md#_snippet_1 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 +} +``` + +---------------------------------------- + +TITLE: Python 3.10 Union Type Annotation Syntax +DESCRIPTION: This snippet demonstrates the modern Python 3.10 syntax for defining type unions using the vertical bar `|` operator. This concise syntax can be used for direct type annotations, offering an alternative to `typing.Union` in many contexts. However, when passing types as arguments (e.g., to FastAPI's `response_model`), `typing.Union` is still required. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/extra-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +some_variable: PlaneItem | CarItem +``` + +---------------------------------------- + +TITLE: Importing and Using BackgroundTasks in FastAPI +DESCRIPTION: This code snippet demonstrates how to import BackgroundTasks and define it as a parameter in a path operation function. FastAPI will automatically create and pass the BackgroundTasks object. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/background-tasks.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import BackgroundTasks, FastAPI + +app = FastAPI() + + +@app.post("/send-notification/{email}") +async def send_notification(email: str, background_tasks: BackgroundTasks): + return {"message": "Notification sent in the background"} +``` + +---------------------------------------- + +TITLE: Define optional query parameter with Union type hint +DESCRIPTION: Demonstrates how to define an optional query parameter using `Union[str, None]` for Python versions prior to 3.10, allowing the parameter to be either a string or `None`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-multiple-params.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +q: Union[str, None] = None +``` + +---------------------------------------- + +TITLE: Get Enum Value +DESCRIPTION: This example demonstrates how to get the string value of an Enum member using `.value`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/path-params.md#_snippet_6 + +LANGUAGE: python +CODE: +``` + return {"model_name": model_name, "message": "Have some residuals": model_name.value} +``` + +---------------------------------------- + +TITLE: FastAPI Internal Handling of Async vs. Sync Functions +DESCRIPTION: This documentation outlines how FastAPI processes different types of functions (path operations, dependencies, utility functions) based on whether they are defined with `def` (synchronous) or `async def` (asynchronous). Understanding these behaviors is key to optimizing application performance and preventing blocking operations in a concurrent environment. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/async.md#_snippet_8 + +LANGUAGE: APIDOC +CODE: +``` +FastAPI Function Type Handling: + +Path Operation Functions: + - `def` (synchronous): + - Execution: Runs in a separate thread from the threadpool. + - Use Case: Suitable for CPU-bound tasks or blocking I/O operations (e.g., traditional database calls). + - Performance Note: FastAPI ensures these don't block the main event loop. + - `async def` (asynchronous): + - Execution: Runs directly in the main event loop. + - Use Case: Ideal for I/O-bound operations that can `await` (e.g., network requests, async database drivers). + - Performance Note: Maximizes concurrency by yielding control during I/O waits. + +Dependencies: + - `def` (synchronous): + - Execution: Runs in a separate thread. + - Behavior: Similar to synchronous path operations, preventing blocking of the event loop. + - `async def` (asynchronous): + - Execution: Runs directly in the event loop. + - Behavior: Similar to asynchronous path operations, allowing for non-blocking I/O within dependencies. + +Sub-dependencies: + - Behavior: Can mix `def` and `async def` dependencies. Synchronous sub-dependencies will be run in a threadpool. + +Other Utility Functions (called by your code): + - `def` (synchronous): + - Execution: Called directly by your code. Does not run in a separate thread unless explicitly managed by you. + - Behavior: If blocking, it will block the calling function/thread. + - `async def` (asynchronous): + - Execution: Must be `await`ed by your code when called. + - Behavior: If not awaited, it returns a coroutine object. Allows for non-blocking operations when properly awaited. +``` + +---------------------------------------- + +TITLE: Access Attributes of Request Body Model +DESCRIPTION: Demonstrates how to access individual attributes of the validated `item` object (an instance of the `Item` Pydantic model) directly within the FastAPI path operation function. This allows for easy manipulation and use of the received data with full type hints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from pydantic import BaseModel + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + +app = FastAPI() + +@app.post("/items/") +async def create_item(item: Item): + print(f"Received item name: {item.name}") + print(f"Received item price: {item.price}") + # You can access all attributes directly + return {"message": "Item received", "item_name": item.name, "item_price": item.price} +``` + +---------------------------------------- + +TITLE: Example HTTP Request with Forbidden Query Parameter +DESCRIPTION: This HTTP request demonstrates an attempt to send an extra, undefined query parameter (`tool=plumbus`) to an endpoint configured to forbid extra fields. This request is expected to trigger an error response from the FastAPI application, showcasing the effect of the `extra='forbid'` configuration. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-param-models.md#_snippet_2 + +LANGUAGE: HTTP +CODE: +``` +https://example.com/items/?limit=10&tool=plumbus +``` + +---------------------------------------- + +TITLE: Declare Header Parameters with Pydantic Model in FastAPI +DESCRIPTION: Demonstrates how to define a Pydantic `BaseModel` to group related HTTP header parameters and then inject this model into a FastAPI path operation using `fastapi.Header`. This approach allows for centralized validation, documentation, and reusability of header definitions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/header-param-models.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from typing import Annotated +from fastapi import FastAPI, Header +from pydantic import BaseModel + +app = FastAPI() + +class CommonHeaders(BaseModel): + x_token: str + x_api_key: Annotated[str | None, Header(alias="X-API-Key")] = None + +@app.get("/items/") +async def read_items(headers: Annotated[CommonHeaders, Header()]): + return {"headers": headers.model_dump()} +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: This command installs FastAPI along with a comprehensive set of standard optional dependencies. These include 'email-validator' for Pydantic, 'httpx', 'jinja2', 'python-multipart' for Starlette, and 'uvicorn' with 'fastapi-cli[standard]' for FastAPI's server and command-line interface. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/README.md#_snippet_9 + +LANGUAGE: Shell +CODE: +``` +pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: FastAPI Optional Dependency Reference +DESCRIPTION: Comprehensive reference for various optional dependencies available for FastAPI, categorized by their primary use and the features they enable, including those part of the 'standard' group and additional ones. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_16 + +LANGUAGE: APIDOC +CODE: +``` +FastAPI Optional Dependencies: + +Standard Dependencies (included with "fastapi[standard]"): + email-validator: + Purpose: For email validation in Pydantic models. + Source: Used by Pydantic. + httpx: + Purpose: Required for using the TestClient. + Source: Used by Starlette. + jinja2: + Purpose: Required for using the default template configuration. + Source: Used by Starlette. + python-multipart: + Purpose: Required for form parsing with request.form(). + Source: Used by Starlette. + uvicorn: + Purpose: Server for loading and serving the application. Includes uvicorn[standard] for high performance. + Source: Used by FastAPI. + fastapi-cli[standard]: + Purpose: Provides the 'fastapi' command. + Source: Used by FastAPI. + fastapi-cloud-cli: + Purpose: Allows deployment to FastAPI Cloud. + Source: Included with fastapi-cli[standard]. + +Additional Optional Dependencies: + Pydantic-related: + pydantic-settings: + Purpose: For settings management. + pydantic-extra-types: + Purpose: For extra types to be used with Pydantic. + FastAPI-specific: + orjson: + Purpose: Required for using ORJSONResponse (faster JSON serialization). + ujson: + Purpose: Required for using UJSONResponse (alternative faster JSON serialization). +``` + +---------------------------------------- + +TITLE: Run FastAPI development server +DESCRIPTION: This command starts the FastAPI development server in development mode, watching for changes in the specified application file. It provides detailed logs including server startup information, URLs for the application, and its interactive documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/first-steps.md#_snippet_0 + +LANGUAGE: console +CODE: +``` +$ fastapi dev main.py + + FastAPI Starting development server 🚀 + + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp + + module 🐍 main.py + + code Importing the FastAPI app object from the module with + the following code: + + from main import app + + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C + to quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. +``` + +---------------------------------------- + +TITLE: Define FastAPI Application Lifespan Events with `lifespan` +DESCRIPTION: This example demonstrates the recommended way to manage application startup and shutdown logic in FastAPI using the `lifespan` parameter with an `asynccontextmanager`. It shows how to initialize resources like a machine learning model before the application starts processing requests and clean them up upon shutdown, ensuring efficient resource management. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/events.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from contextlib import asynccontextmanager +from fastapi import FastAPI + +models = {} # Simulate a shared resource + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup logic: Load resources + print("Loading ML model...") + models["my_model"] = {"status": "loaded"} # Placeholder for actual model + yield # Application starts processing requests + # Shutdown logic: Unload resources + print("Unloading ML model...") + models.clear() + +app = FastAPI(lifespan=lifespan) + +@app.get("/") +async def read_root(): + return {"message": "Hello World", "model_status": "loaded" if "my_model" in models else "not loaded"} +``` + +---------------------------------------- + +TITLE: FastAPI Dependency with Yield: Catching but Not Re-raising Exceptions +DESCRIPTION: Illustrates a FastAPI dependency using `yield` where an exception is caught within the `try...except` block but *not* re-raised. This leads to a generic 500 Internal Server Error for the client without proper server-side logging or indication of the original error, making debugging difficult. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_1 + +LANGUAGE: Python 3.9+ +CODE: +``` +from typing import Annotated +from fastapi import Depends, FastAPI + +app = FastAPI() + +async def get_resource_no_reraise(): + try: + print("Acquiring resource...") + yield "resource_data" + except Exception as e: + print(f"Caught exception but not re-raising: {e}") + # No 'raise' here + finally: + print("Releasing resource...") + +@app.get("/data/") +async def get_data( + res: Annotated[str, Depends(get_resource_no_reraise)] +): + # This error will be caught by the dependency but not re-raised + raise ValueError("Simulated error in route processing") +``` + +---------------------------------------- + +TITLE: Install email-validator for Pydantic EmailStr +DESCRIPTION: Commands to install the `email-validator` library, which is required for Pydantic's `EmailStr` type. It provides options for installing directly or via Pydantic's extra dependencies. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/response-model.md#_snippet_4 + +LANGUAGE: bash +CODE: +``` +pip install email-validator +pip install "pydantic[email]" +``` + +---------------------------------------- + +TITLE: Format Pull Request Title for Translations +DESCRIPTION: Example of how to format a Pull Request title specifically for translations. It includes the use of the 'globe with meridians' gitmoji (🌐) and the full path to the translated file, adhering to the imperative verb structure required for automated release notes generation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/management-tasks.md#_snippet_0 + +LANGUAGE: Markdown +CODE: +``` +🌐 Add Spanish translation for `docs/es/docs/teleporting.md` +``` + +---------------------------------------- + +TITLE: Complex Dependency Chain Visualization in FastAPI +DESCRIPTION: This Mermaid diagram illustrates a more intricate dependency graph, demonstrating how dependencies can be nested or chained. It shows how a `current_user` dependency can lead to `active_user`, which then branches into `admin_user` and `paying_user`, each enabling access to specific API endpoints, showcasing advanced dependency management. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/dependencies/index.md#_snippet_4 + +LANGUAGE: mermaid +CODE: +``` +graph TB + +current_user(["current_user"]) +active_user(["active_user"]) +admin_user(["admin_user"]) +paying_user(["paying_user"]) + +public["/items/public/"] +private["/items/private/"] +activate_user["/users/{user_id}/activate"] +pro_items["/items/pro/"] + +current_user --> active_user +active_user --> admin_user +active_user --> paying_user + +current_user --> public +active_user --> private +admin_user --> activate_user +paying_user --> pro_items +``` + +---------------------------------------- + +TITLE: Pydantic model definitie +DESCRIPTION: Definieert een Pydantic model `User` met type hints voor de attributen `id` (int), `name` (str) en `joined` (date). Pydantic gebruikt deze type declaraties voor data validatie en serialisatie. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/features.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +# Een Pydantic model +class User(BaseModel): + id: int + name: str + joined: date +``` + +---------------------------------------- + +TITLE: Accessing Model Attributes in FastAPI +DESCRIPTION: Demonstrates how to access attributes of a Pydantic model within a FastAPI function. The item object, an instance of the Item model, is passed as a parameter, and its attributes (name, description, price, tax) are accessed directly. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Item): + return item +``` + +---------------------------------------- + +TITLE: Annotated Type Hint Example (Python 3.8+) +DESCRIPTION: This example shows how to use `Annotated` to add metadata to type hints in Python versions lower than 3.9. `Annotated` is imported from `typing_extensions`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/python-types.md#_snippet_23 + +LANGUAGE: Python +CODE: +``` +{!> ../../docs_src/python_types/tutorial013.py!} +``` + +---------------------------------------- + +TITLE: Define SQLModel Hero Class +DESCRIPTION: Defines a SQLModel 'Hero' class that represents a table in the SQL database. It includes fields like 'id' (primary key), 'name' (indexed), 'secret_name', and 'age' (indexed), demonstrating how to map Python types to SQL column types and define constraints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/sql-databases.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from typing import Optional + +from sqlmodel import Field, SQLModel + + +class Hero(SQLModel, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + name: str = Field(index=True) + secret_name: str + age: Optional[int] = Field(default=None, index=True) +``` + +---------------------------------------- + +TITLE: List Field Declaration +DESCRIPTION: This code snippet demonstrates how to declare a list field in a Pydantic model. The `tags` attribute is defined as a `list`, but the type of elements within the list is not specified. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body-nested-models.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list = [] +``` + +---------------------------------------- + +TITLE: Example JSON request body with multiple models and singular value +DESCRIPTION: Shows the expected JSON structure for a FastAPI path operation that combines multiple Pydantic models (`item`, `user`) with an additional singular value (`importance`) directly within the same request body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-multiple-params.md#_snippet_5 + +LANGUAGE: JSON +CODE: +``` +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + }, + "user": { + "username": "dave", + "full_name": "Dave Grohl" + }, + "importance": 5 +} +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter Default Value with Annotated +DESCRIPTION: Illustrates the correct and incorrect ways to define default values for query parameters when using `Annotated` with `Query`. It highlights that the function parameter's default value should be used, not `Query`'s `default` argument, to avoid ambiguity. Also shows the older style without `Annotated`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +q: Annotated[str, Query(default="rick")] = "morty" +``` + +LANGUAGE: Python +CODE: +``` +q: Annotated[str, Query()] = "rick" +``` + +LANGUAGE: Python +CODE: +``` +q: str = Query(default="rick") +``` + +---------------------------------------- + +TITLE: Creating an Enum Class for Path Parameters in FastAPI +DESCRIPTION: This code snippet demonstrates how to create an Enum class in Python to define a set of valid string values for a path parameter in a FastAPI application. It inherits from both `str` and `Enum` to ensure the values are strings and part of the enumeration. The `Enum` is used to restrict the possible values of a path parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/tutorial/path-params.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +from enum import Enum + + +class ModelName(str, Enum): + alexnet = "alexnet" + resnet = "resnet" + lenet = "lenet" +``` + +---------------------------------------- + +TITLE: Python Import Equivalent for Uvicorn Command +DESCRIPTION: Illustrates the Python import statement equivalent to how Uvicorn locates the FastAPI application object (`app`) within the `main.py` module. This clarifies the `main:app` syntax used in the Uvicorn command-line interface. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/deployment/manually.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from main import app +``` + +---------------------------------------- + +TITLE: Understanding Single-Dot Relative Import +DESCRIPTION: Illustrates the use of a single dot (`.`) in Python relative imports, indicating a module within the same package. This example shows an attempt to import `get_token_header` from a `dependencies` module expected in the same directory as the current file. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/bigger-applications.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from .dependencies import get_token_header +``` + +---------------------------------------- + +TITLE: FastAPI Application with Tags +DESCRIPTION: This example demonstrates a FastAPI application that uses tags to separate different groups of path operations, specifically for 'items' and 'users'. It shows how to define tags in the FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/advanced/generate-clients.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}", tags=["items"]) +async def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.post("/items/", tags=["items"]) +async def create_item(name: str, price: float): + return {"name": name, "price": price} + + +@app.get("/users/{user_id}", tags=["users"]) +async def read_user(user_id: int, q: Union[str, None] = None): + return {"user_id": user_id, "q": q} + + +@app.post("/users/", tags=["users"]) +async def create_user(name: str, age: int): + return {"name": name, "age": age} +``` + +---------------------------------------- + +TITLE: Define Pydantic Model with HttpUrl Type +DESCRIPTION: Illustrates the use of Pydantic's `HttpUrl` type for an attribute, which automatically validates if the input string is a valid URL and provides appropriate documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel, HttpUrl + +class Image(BaseModel): + url: HttpUrl + name: str +``` + +---------------------------------------- + +TITLE: Define PUT Request with Pydantic Model in FastAPI +DESCRIPTION: This code defines a PUT endpoint `/items/{item_id}` in a FastAPI application that accepts a request body of type `Item`, which is a Pydantic model. The `Item` model defines the expected structure of the request body, including fields like `name`, `price`, and `is_offer`. The function `update_item` handles the PUT request and returns a dictionary containing the item's name and ID. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/index.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Defining a Pydantic Model in Python +DESCRIPTION: This code defines a Pydantic model named `User` with type annotations for its attributes (id, name, joined). Pydantic uses these type hints to perform data validation and serialization. The example demonstrates how to create instances of the `User` model using both direct instantiation and dictionary unpacking. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fa/docs/features.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +# A Pydantic model +class User(BaseModel): + id: int + name: str + joined: date +``` + +---------------------------------------- + +TITLE: Testing FastAPI App in test_main.py +DESCRIPTION: This snippet demonstrates how to test a FastAPI application when the app is defined in a separate `main.py` file. It imports the `app` instance using relative imports and then uses TestClient to send a request and assert the response. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/testing.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from fastapi.testclient import TestClient + +from .main import app + + +client = TestClient(app) + + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"Hello": "World"} +``` + +---------------------------------------- + +TITLE: FastAPI Endpoint Using Pydantic Model as Input +DESCRIPTION: Demonstrates a FastAPI `POST` endpoint (`/items/`) that accepts an `Item` Pydantic model as input. This example highlights how the `description` field, having a default `None` value, is considered optional for input, affecting the generated OpenAPI schema. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/how-to/separate-openapi-schemas.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Item): + return item +``` + +---------------------------------------- + +TITLE: Using Depends in WebSocket Endpoints +DESCRIPTION: Demonstrates how to use the `Depends` function to inject dependencies into a WebSocket endpoint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/advanced/websockets.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +async def get_cookie_or_token(websocket: WebSocket, cookie: Optional[str] = Cookie(None), token: Optional[str] = None): + if cookie is None and token is None: + raise WebSocketException(code=1008, reason="No cookies or token received") + if cookie: + return cookie + return token + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket, q: Optional[str] = None, cookie_or_token: str = Depends(get_cookie_or_token)): + ... +``` + +---------------------------------------- + +TITLE: Illustrating Python class instantiation as a callable +DESCRIPTION: This example demonstrates that a Python class itself is a callable, as its instantiation (`Cat(name="Mr Fluffy")`) uses function-like call syntax. This property allows FastAPI to treat classes as dependencies, processing their `__init__` parameters. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +class Cat: + def __init__(self, name: str): + self.name = name + + +fluffy = Cat(name="Mr Fluffy") +``` + +---------------------------------------- + +TITLE: List Field with Type Parameter +DESCRIPTION: This code snippet demonstrates how to declare a list field with a specific type parameter (string) in a Pydantic model. The `tags` attribute is defined as a `list` of strings. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body-nested-models.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list[str] = [] +``` + +---------------------------------------- + +TITLE: 의존성 (디펜더블) 생성하기 - Python +DESCRIPTION: 경로 작동 함수가 가질 수 있는 모든 매개변수를 갖는 단순한 함수를 의존성으로 정의합니다. 이 함수는 선택적인 쿼리 매개변수 q, skip, limit을 받아들이고, 이들을 포함하는 dict를 반환합니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/dependencies/index.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from typing import Optional + +from fastapi import Depends, FastAPI + +app = FastAPI() + + +def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} +``` + +---------------------------------------- + +TITLE: Declare Request Body Parameter in FastAPI Path Operation +DESCRIPTION: Shows how to integrate the Pydantic `Item` model into a FastAPI path operation function. By type-hinting a function parameter (e.g., `item`) with the `Item` model, FastAPI automatically reads the request body as JSON, validates it against the model's schema, and provides it as a Python object for use within the function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +async def create_item(item: Item): +``` + +---------------------------------------- + +TITLE: Create a Path Operation for Testing +DESCRIPTION: This snippet creates a simple path operation to test if the custom documentation setup is working correctly. It defines a GET endpoint at the root path that returns a dictionary with a message. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/how-to/custom-docs-ui-assets.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.get("/") +async def read_root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Declare Dependencies in FastAPI Path Operations +DESCRIPTION: These examples demonstrate how to integrate a dependency function (`common_parameters`) into FastAPI path operations using `Depends`. By assigning `Depends(common_parameters)` to a parameter, FastAPI automatically resolves and injects the result of `common_parameters` into `read_items` and `read_users` functions, promoting code reuse. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/dependencies/index.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI, Depends + +app = FastAPI() + +def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): + return {"q": q, "skip": skip, "limit": limit} + +@app.get("/items/") +async def read_items(commons: dict = Depends(common_parameters)): + return commons + +@app.get("/users/") +async def read_users(commons: dict = Depends(common_parameters)): + return commons +``` + +---------------------------------------- + +TITLE: Declaring a Class as a Type +DESCRIPTION: Demonstrates how to declare a class as a type for a variable in Python. This allows for editor support and type checking when working with instances of the class. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_16 + +LANGUAGE: Python +CODE: +``` +class Person: + def __init__(self, name: str): + self.name = name +``` + +---------------------------------------- + +TITLE: Declare Single Body Parameter with Key +DESCRIPTION: Illustrates how to use the `embed` parameter of `Body` to specify that a single body parameter should be expected within a JSON with a specific key. This is similar to how multiple body parameters are handled. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/body-multiple-params.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +@app.put("/items/{item_id}") +async def update_item( + item_id: int, + item: Item = Body(embed=True), +): + results = {"item_id": item_id, "item": item} + return results +``` + +---------------------------------------- + +TITLE: Type Hints for Function Return Values and Static Analysis +DESCRIPTION: This example illustrates the use of type hints for function return values, enabling static analysis tools to detect potential type mismatches. It shows how a tool can flag an error when an integer is incorrectly concatenated with a string. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +{!../../docs_src/python_types/tutorial003.py!} +``` + +---------------------------------------- + +TITLE: Python: Example Usage of functools.lru_cache +DESCRIPTION: This simple Python function demonstrates the behavior of `@lru_cache`. When applied to a function, `lru_cache` caches the results of function calls based on their arguments. Subsequent calls with the same arguments return the cached result without re-executing the function body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/advanced/settings.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +from functools import lru_cache + +@lru_cache +def say_hi(name: str, salutation: str = "Ms."): + return f"Hello {salutation} {name}" +``` + +---------------------------------------- + +TITLE: Define a Synchronous Function in Python +DESCRIPTION: Shows a standard synchronous function definition using `def`. This type of function executes sequentially and blocks the program's execution until it completes, unlike asynchronous functions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/async.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +# This is not asynchronous +def get_sequential_burgers(number: int): + # Do some sequential stuff to create the burgers + return burgers +``` + +---------------------------------------- + +TITLE: Declaring a Complex Object Type +DESCRIPTION: This snippet shows how to declare a more complex object type, such as an `Item`, in FastAPI. This allows FastAPI to validate and convert data for complex JSON structures. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pl/docs/index.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +item: Item +``` + +---------------------------------------- + +TITLE: Importing FastAPI Class in Python +DESCRIPTION: This code snippet demonstrates how to import the FastAPI class from the fastapi library. The FastAPI class provides all the necessary functionality for building an API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +``` + +---------------------------------------- + +TITLE: Install FastAPI without Optional Dependencies +DESCRIPTION: Shows the command to install FastAPI with only its core dependencies, excluding any optional packages for a minimal setup. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_14 + +LANGUAGE: Python +CODE: +``` +pip install fastapi +``` + +---------------------------------------- + +TITLE: Declaring an Optional Type - Python 3.10+ +DESCRIPTION: This snippet declares a variable `name` that can be either a string or None using the union operator `|`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_15 + +LANGUAGE: Python +CODE: +``` +name: str | None = "Guido" +``` + +---------------------------------------- + +TITLE: Run FastAPI Development Server with Auto-Reload +DESCRIPTION: This console command initiates the FastAPI development server using `fastapi dev main.py`. It automatically detects changes in `main.py` and reloads the application, providing a convenient workflow for local development. The output shows the server's local address and links to the API documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/id/docs/index.md#_snippet_2 + +LANGUAGE: Console +CODE: +``` +$ fastapi dev main.py + + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + │ fastapi run │ + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +---------------------------------------- + +TITLE: FastAPI/Starlette TrustedHostMiddleware API +DESCRIPTION: API documentation for `TrustedHostMiddleware`, which protects against HTTP Host Header attacks by validating the `Host` header against a list of allowed domains. This middleware is crucial for securing applications against certain types of web vulnerabilities. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/middleware.md#_snippet_5 + +LANGUAGE: APIDOC +CODE: +``` +TrustedHostMiddleware: + __init__(app: ASGIApp, allowed_hosts: List[str]) + app: The ASGI application instance. + allowed_hosts: A list of domain names that should be allowed as hostnames. Wildcard domains such as "*.example.com" are supported for matching subdomains. To allow any hostname, use allowed_hosts=["*"] or omit the middleware. + Behavior: + - If an incoming request does not validate correctly, a 400 Bad Request response will be sent. +``` + +---------------------------------------- + +TITLE: Query Parameters with Defaults in FastAPI (Python) +DESCRIPTION: This code snippet demonstrates how to define query parameters with default values in a FastAPI endpoint. The `skip` parameter defaults to 0 and the `limit` parameter defaults to 10. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/tr/docs/tutorial/query-params.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/") +async def read_items(skip: int = 0, limit: int = 10): + return {"skip": skip, "limit": limit} +``` + +---------------------------------------- + +TITLE: Example Python Data with Non-Default Values +DESCRIPTION: Presents a Python dictionary representing data where some fields (`description`, `tax`) have values different from their Pydantic model defaults. This demonstrates that such fields will always be included in the response, even with `response_model_exclude_unset=True`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/response-model.md#_snippet_12 + +LANGUAGE: Python +CODE: +``` +{ + "name": "Bar", + "description": "The bartenders", + "price": 62, + "tax": 20.2 +} +``` + +---------------------------------------- + +TITLE: Define Path Operation Function (Sync) +DESCRIPTION: This is a path operation function defined as a regular Python function instead of an async function. FastAPI will call it every time it receives a request to the URL / using a GET operation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Install passlib with bcrypt +DESCRIPTION: Installs the passlib library with the bcrypt extra, which is used for handling password hashing securely in Python. Bcrypt is the recommended algorithm. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/security/oauth2-jwt.md#_snippet_1 + +LANGUAGE: console +CODE: +``` +$ pip install passlib[bcrypt] + +---> 100% +``` + +---------------------------------------- + +TITLE: Expected JSON Body with Embedded Item +DESCRIPTION: This JSON snippet shows the expected structure of the request body when the `embed` parameter is set to `True`. The `Item` model's data is nested under the 'item' key. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/tutorial/body-multiple-params.md#_snippet_5 + +LANGUAGE: JSON +CODE: +``` +{ + "item": { + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2 + } +} +``` + +---------------------------------------- + +TITLE: Install FastAPI with Standard Dependencies +DESCRIPTION: This command installs FastAPI along with a comprehensive set of standard optional dependencies. These include 'email-validator' for Pydantic, 'httpx', 'jinja2', 'python-multipart' for Starlette, and 'uvicorn' with 'fastapi-cli[standard]' for FastAPI's server and command-line interface. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/index.md#_snippet_9 + +LANGUAGE: Shell +CODE: +``` +pip install "fastapi[standard]" +``` + +---------------------------------------- + +TITLE: Defining a Sub-Model +DESCRIPTION: This code snippet defines a Pydantic sub-model named `Image` with `url` and `name` attributes. This model can be used as a type for other model attributes, enabling nested data structures. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body-nested-models.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Image(BaseModel): + url: str + name: str +``` + +---------------------------------------- + +TITLE: Dependency with yield and try/finally +DESCRIPTION: Illustrates how to use `try` and `finally` blocks within a dependency that uses `yield` to handle exceptions and ensure that cleanup code is always executed, regardless of whether an exception occurs. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +db = DBSession() +try: + yield db +except SomeException: + db.rollback() +finally: + db.close() +``` + +---------------------------------------- + +TITLE: Creating a FastAPI Instance +DESCRIPTION: This code snippet shows how to create an instance of the FastAPI class. This instance is used to define the API endpoints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/first-steps.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +app = FastAPI() +``` + +---------------------------------------- + +TITLE: Pydantic model instantiering +DESCRIPTION: Demonstreert hoe een Pydantic model `User` wordt geïnstantieerd met data. De eerste instantie gebruikt keyword argumenten, de tweede gebruikt een dictionary die wordt uitgepakt met `**` om de argumenten door te geven. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/nl/docs/features.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +---------------------------------------- + +TITLE: Install and Run FastAPI Development Server with CLI +DESCRIPTION: Demonstrates how to install FastAPI and use the new `fastapi dev` command to start a development server. The output shows the server address, API documentation URL, and notes about development mode versus production (`fastapi run`). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_8 + +LANGUAGE: Shell +CODE: +``` +$ pip install --upgrade fastapi + +$ fastapi dev main.py + + + ╭────────── FastAPI CLI - Development mode ───────────╮ + │ │ + │ Serving at: http://127.0.0.1:8000 │ + │ │ + │ API docs: http://127.0.0.1:8000/docs │ + │ │ + │ Running in development mode, for production use: │ + │ │ + │ fastapi run │ + │ │ + ╰─────────────────────────────────────────────────────╯ + +INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +INFO: Started reloader process [2248755] using WatchFiles +INFO: Started server process [2248757] +INFO: Waiting for application startup. +INFO: Application startup complete. +``` + +---------------------------------------- + +TITLE: FastAPI Dependency Shorthand with `Depends()` +DESCRIPTION: Explains and demonstrates the shorthand syntax for `Depends()`. When the dependency type is already specified in the type hint (e.g., `commons: CommonQueryParams`), `Depends()` can be used without explicitly passing the class, making the code more concise and readable. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +LANGUAGE: Python +CODE: +``` +commons = Depends(CommonQueryParams) +``` + +LANGUAGE: Python +CODE: +``` +commons: CommonQueryParams = Depends() +``` + +LANGUAGE: Python +CODE: +``` +@app.get("/items/") +async def read_items(commons: CommonQueryParams = Depends()): + return {"q": commons.q, "skip": commons.skip, "limit": commons.limit} +``` + +---------------------------------------- + +TITLE: Conceptual Example: Adding Third-Party ASGI Middleware +DESCRIPTION: Illustrates the general pattern for integrating a third-party ASGI middleware, where the middleware class wraps an existing ASGI application. This is a conceptual example showing how such middlewares are typically designed to receive an ASGI app as their first argument. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/middleware.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from unicorn import UnicornMiddleware + +app = SomeASGIApp() + +new_app = UnicornMiddleware(app, some_config="rainbow") +``` + +---------------------------------------- + +TITLE: Configure FastAPI APIRouter with Shared Settings +DESCRIPTION: This snippet demonstrates how to initialize an `APIRouter` instance with common settings like a `prefix`, `tags`, `responses`, and `dependencies`. This approach centralizes configuration for a group of routes, reducing boilerplate and ensuring consistency across related endpoints. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/bigger-applications.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import APIRouter, Depends, HTTPException, status +from app.dependencies import get_x_token + +router = APIRouter( + prefix="/items", + tags=["items"], + responses={404: {"description": "Not found"}}, + dependencies=[Depends(get_x_token)], +) + +@router.get("/") +async def read_items(): + return ["Portal gun", "Plumbus"] + +@router.get("/{item_id}") +async def read_item(item_id: str): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Single Values in Body with FastAPI +DESCRIPTION: This example shows how to include a single value in the request body alongside Pydantic models using the `Body` parameter. FastAPI will expect a JSON body containing the item, user, and importance. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/body-multiple-params.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +@app.post("/items/") +async def create_item( + item: Item, + user: User, + importance: int = Body(gt=0), +): + return {"item": item, "user": user, "importance": importance} +``` + +---------------------------------------- + +TITLE: Dict 타입 힌트 예제 +DESCRIPTION: 이 예제는 `dict`에 대한 타입 힌트를 선언하는 방법을 보여줍니다. `typing` 모듈을 사용하여 키와 값의 타입을 지정할 수 있습니다. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/python-types.md#_snippet_7 + +LANGUAGE: python +CODE: +``` +from typing import Dict +``` + +LANGUAGE: python +CODE: +``` +prices: Dict[str, float] +``` + +---------------------------------------- + +TITLE: WebSocket Endpoint with Dependencies +DESCRIPTION: Shows how to use dependencies, security, and other FastAPI features within a WebSocket endpoint. Includes examples of using `Depends` and raising `WebSocketException`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/advanced/websockets.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import Cookie, Depends, FastAPI, Header, WebSocket, WebSocketException + +app = FastAPI() + +async def get_cookie_or_token(websocket: WebSocket, cookie: Optional[str] = Cookie(None), token: Optional[str] = None): + if cookie is None and token is None: + raise WebSocketException(code=1008, reason="No cookies or token received") + if cookie: + return cookie + return token + +@app.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket, q: Optional[str] = None, cookie_or_token: str = Depends(get_cookie_or_token)): + await websocket.accept() + while True: + data = await websocket.receive_text() + await websocket.send_text(f"Message text was: {data}, query parameter q is: {q}, cookie_or_token is: {cookie_or_token}") +``` + +---------------------------------------- + +TITLE: Initializing FastAPI with Global Dependencies +DESCRIPTION: Shows how to initialize the main `FastAPI` application instance and declare global dependencies that apply to all path operations across the application, including those defined in included `APIRouter` instances. This ensures consistent dependency injection. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/bigger-applications.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Depends +from .dependencies import get_query_token # Example global dependency + +app = FastAPI(dependencies=[Depends(get_query_token)]) +``` + +---------------------------------------- + +TITLE: Defining a Request Body with Pydantic and Adding a PUT Route +DESCRIPTION: This code defines a Pydantic model `Item` to represent the request body for a PUT request. It then adds a PUT route '/items/{item_id}' that accepts an item_id and an Item object in the request body. The function returns the item name and item ID. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/az/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Defining a Path Operation Function (Sync) +DESCRIPTION: This code snippet shows how to define a synchronous path operation function. This function will be called whenever FastAPI receives a GET request to the `/` URL. It returns a dictionary that will be converted to JSON. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/first-steps.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Python File Handling with `with` Statement +DESCRIPTION: Demonstrates the use of Python's `with` statement for safe file handling. The `open()` function returns a context manager, ensuring that the file is automatically closed upon exiting the `with` block, even if errors occur. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +---------------------------------------- + +TITLE: Import HTTPException in FastAPI +DESCRIPTION: This snippet shows how to import the `HTTPException` class from the `fastapi` library, which is the primary tool for raising HTTP errors in your application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/handling-errors.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import HTTPException +``` + +---------------------------------------- + +TITLE: JSON Response Example +DESCRIPTION: Example JSON response from the /items/{item_id} endpoint with a query parameter. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/deployment/docker.md#_snippet_4 + +LANGUAGE: JSON +CODE: +``` +{"item_id": 5, "q": "somequery"} +``` + +---------------------------------------- + +TITLE: Python File Handling with Context Manager +DESCRIPTION: This snippet demonstrates the use of a Python `with` statement to open and read a file. The `with` statement ensures that the file is automatically closed after the block is exited, even if exceptions occur, showcasing a fundamental application of context managers. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +with open("./somefile.txt") as f: + contents = f.read() + print(contents) +``` + +---------------------------------------- + +TITLE: Using a Sub-Model as a Type +DESCRIPTION: This code snippet demonstrates how to use the `Image` sub-model as a type for an attribute in another Pydantic model (`Item`). This allows for nested JSON objects with specific attribute names, types, and validations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/body-nested-models.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Image(BaseModel): + url: str + name: str + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: list[str] = [] + image: Optional[Image] = None +``` + +---------------------------------------- + +TITLE: FastAPI Dependency Declaration: Explicit Class Reference +DESCRIPTION: This snippet shows the standard method for declaring a class-based dependency in FastAPI. It requires explicitly passing the dependency class (e.g., `CommonQueryParams`) to `Depends()`, leading to some code repetition. Both `Annotated` and non-`Annotated` syntax are provided. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] +``` + +LANGUAGE: Python +CODE: +``` +commons: CommonQueryParams = Depends(CommonQueryParams) +``` + +---------------------------------------- + +TITLE: FastAPI Test File +DESCRIPTION: This example shows how to create a test file (test_main.py) to test the FastAPI application defined in main.py. It imports the app object from main.py using relative imports. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/testing.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from fastapi.testclient import TestClient + +from .main import app + + +client = TestClient(app) + + +def test_read_main(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"msg": "Hello World"} +``` + +---------------------------------------- + +TITLE: Using a Nested Model as a Type +DESCRIPTION: Shows how to use the `Image` model as a type for the `image` attribute in the `Item` model. This allows for nested JSON objects with specific attribute names, types, and validation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/body-nested-models.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from pydantic import BaseModel + + +class Image(BaseModel): + url: str + name: str + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + tags: set[str] = set() + image: Optional[Image] = None +``` + +---------------------------------------- + +TITLE: Dependency with yield and raise +DESCRIPTION: Demonstrates how to properly re-raise an exception caught in the `except` block of a dependency using `yield`. This ensures that FastAPI is aware of the error and can handle it appropriately. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +async def dependency_a(): + try: + yield + except InternalError: + raise +``` + +---------------------------------------- + +TITLE: Understanding Triple-Dot Relative Import (Invalid Example) +DESCRIPTION: Demonstrates an invalid use of triple dots (`...`) in Python relative imports. This attempts to import from a grandparent package, which is not applicable in the given FastAPI application structure, leading to an import error as the target package does not exist. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/bigger-applications.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +from ...dependencies import get_token_header +``` + +---------------------------------------- + +TITLE: Ordering Path and Query Parameters +DESCRIPTION: This code snippet shows how to order path and query parameters in a FastAPI function. It demonstrates that FastAPI can detect parameters by their names, types, and default definitions, regardless of their order. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/path-params-numeric-validations.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_items(item_id: int, q: str): + return {"item_id": item_id, "q": q} +``` + +---------------------------------------- + +TITLE: Initialize FastAPI Application and Include Routers +DESCRIPTION: Shows the main application file (`main.py`) where the `FastAPI` instance is created and `APIRouter` instances from different modules (e.g., `items`, `users`) are included using `app.include_router()`. This demonstrates how to aggregate routes from various parts of a larger application into a single main application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/bigger-applications.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +from .routers import items, users + +app = FastAPI() + +app.include_router(items.router) +app.include_router(users.router) +``` + +---------------------------------------- + +TITLE: Return Content from FastAPI Route +DESCRIPTION: Demonstrates returning a dictionary from a FastAPI route function. FastAPI automatically converts dictionaries, lists, and singular values to JSON responses. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/first-steps.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Declare typed path parameters in FastAPI +DESCRIPTION: Illustrates how to add type annotations to path parameters in FastAPI. This enables automatic data conversion (e.g., string to integer) and provides enhanced editor support, including error checking and code completion, improving development efficiency. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_item(item_id: int): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Importing HTTPException in FastAPI +DESCRIPTION: This code snippet demonstrates how to import the HTTPException class from the fastapi module. HTTPException is used to raise HTTP exceptions within FastAPI applications. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/handling-errors.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi import HTTPException +``` + +---------------------------------------- + +TITLE: FastAPI Asynchronous Function Handling +DESCRIPTION: This section details how FastAPI processes different types of functions (path operations, dependencies, sub-dependencies, and other utility functions) based on whether they are defined with `async def` or `def`. It explains the performance implications and FastAPI's internal mechanisms, such as using a thread pool for synchronous functions to prevent blocking the main event loop. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/async.md#_snippet_2 + +LANGUAGE: APIDOC +CODE: +``` +FastAPI Function Handling: + +Path Operation Functions: + - async def: Runs directly in the event loop. Ideal for I/O-bound operations (e.g., database queries, network requests). + - def: Runs in a separate thread pool. Use for CPU-bound operations or blocking I/O to avoid blocking the main event loop. + +Dependencies: + - async def: Runs directly in the event loop. + - def: Runs in a separate thread pool. + +Sub-dependencies: + - Can mix async def and def. def sub-dependencies will be executed in a thread pool. + +Other Utility Functions: + - def: Called directly by your code; no threading is applied by FastAPI. + - async def: Must be explicitly `await`ed by your calling code. +``` + +---------------------------------------- + +TITLE: Expected JSON Payload for Nested Model +DESCRIPTION: An example of the JSON structure that FastAPI expects when a Pydantic model includes another Pydantic model as a nested attribute. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_6 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Foo", + "description": "The pretender", + "price": 42.0, + "tax": 3.2, + "tags": ["rock", "metal", "bar"], + "image": { + "url": "http://example.com/baz.jpg", + "name": "The Foo live" + } +} +``` + +---------------------------------------- + +TITLE: Declaring a Complex Item Model in FastAPI +DESCRIPTION: This code snippet shows how to declare a more complex item model in FastAPI using Python type hints. The `item: Item` syntax indicates that the `item` parameter should be an instance of the `Item` class. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/he/docs/index.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +item: Item +``` + +---------------------------------------- + +TITLE: Declaring Variables with Type Hints in Python +DESCRIPTION: This code demonstrates how to declare a variable with a type hint in Python using standard Python type declarations. It allows for editor support inside the function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/features.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from datetime import date + +from pydantic import BaseModel + +# Declare a variable as a str +# and get editor support inside the function +def main(user_id: str): + return user_id + + +# A Pydantic model +class User(BaseModel): + id: int + name: str + joined: date +``` + +---------------------------------------- + +TITLE: FastAPI Dependency with Yield and Implicit Exception Handling (Old Behavior) +DESCRIPTION: This code demonstrates the previous behavior of FastAPI dependencies using `yield` where exceptions caught within a `try...except` block were not required to be re-raised. This pattern, while seemingly convenient, could lead to unhandled memory issues if exceptions were silently consumed. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_9 + +LANGUAGE: Python +CODE: +``` +def my_dep(): + try: + yield + except SomeException: + pass +``` + +---------------------------------------- + +TITLE: Define Path Operation Function (Async) +DESCRIPTION: This is a path operation function. FastAPI will call it every time it receives a request to the URL / using a GET operation. This example uses an async function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/first-steps.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +async def root(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Getting Enum Value in FastAPI (Python) +DESCRIPTION: This snippet shows how to retrieve the actual value (a string in this case) from an Enum member using `.value`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/path-params.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +return {"model_name": model_name, "message": "Have some residuals", "value": model_name.value} +``` + +---------------------------------------- + +TITLE: FastAPI Path Parameter Numeric Validation: Floats with Greater Than and Less Than +DESCRIPTION: This snippet illustrates applying numeric validations to a `float` path parameter. It uses 'greater than' (`gt`) and 'less than' (`lt`) to ensure the `item_id` is a float strictly between 0 and 1 (e.g., 0.5 is valid, but 0.0 or 1.0 are not). + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params-numeric-validations.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated +from fastapi import FastAPI, Path + +app = FastAPI() + +@app.get("/items/{item_id}") +async def read_items( + item_id: Annotated[float, Path(title="The ID of the item to get", gt=0, lt=1)] +): + return {"item_id": item_id} +``` + +---------------------------------------- + +TITLE: Translating Warning Blocks +DESCRIPTION: This snippet demonstrates the translation of a 'warning' block. The English term 'warning' is translated to 'Advertencia' in Spanish, separated by a vertical bar. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/llm-prompt.md#_snippet_3 + +LANGUAGE: Text +CODE: +``` +/// warning | Advertencia +``` + +---------------------------------------- + +TITLE: PATH Variable Example (Linux, macOS) +DESCRIPTION: This is an example of how the PATH environment variable might look on Linux and macOS systems. It consists of a series of directories separated by colons. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/environment-variables.md#_snippet_6 + +LANGUAGE: plaintext +CODE: +``` +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +---------------------------------------- + +TITLE: Import FastAPI Response Class +DESCRIPTION: This snippet demonstrates the standard way to import the `Response` class from the `fastapi` library, enabling its use for custom HTTP response handling within FastAPI applications. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/response.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import Response +``` + +---------------------------------------- + +TITLE: FastAPI Query Parameter List and Multiple Values +DESCRIPTION: Illustrates how to define a query parameter (`q`) that accepts multiple values from the URL, which FastAPI automatically collects into a Python list. This example uses Python 3.10+ type hints (`list[str] | None`). It also includes an example JSON response demonstrating the collected list. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Query + +app = FastAPI() + +@app.get("/items/") +async def read_items(q: list[str] | None = Query(default=None)): + return {"q": q} +``` + +LANGUAGE: JSON +CODE: +``` +{ + "q": [ + "foo", + "bar" + ] +} +``` + +---------------------------------------- + +TITLE: Compare HTTP GET operations: Requests client vs. FastAPI server +DESCRIPTION: This snippet demonstrates the design philosophy shared between the 'requests' library for making HTTP requests (client-side) and FastAPI for defining API endpoints (server-side). Both aim for an intuitive API where HTTP method names are directly used for operations, showcasing a parallel in their approach to handling GET requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/alternatives.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +response = requests.get("http://example.com/some/url") +``` + +LANGUAGE: Python +CODE: +``` +@app.get("/some/url") +def read_url(): + return {"message": "Hello World"} +``` + +---------------------------------------- + +TITLE: Variable of Type Class +DESCRIPTION: Shows how to declare a variable with the type of a previously defined class. This enables type hinting and editor support for the variable. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/python-types.md#_snippet_17 + +LANGUAGE: Python +CODE: +``` +some_person: Person = Person(name="John") +``` + +---------------------------------------- + +TITLE: FastAPI Built-in Response Classes Reference +DESCRIPTION: Comprehensive documentation for various built-in `Response` classes available in FastAPI (mostly from Starlette). This includes the base `Response` class and its parameters, `HTMLResponse`, `PlainTextResponse`, `JSONResponse`, and `ORJSONResponse`, detailing their purpose and usage. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/advanced/custom-response.md#_snippet_6 + +LANGUAGE: APIDOC +CODE: +``` +Response: + __init__(content: Union[str, bytes], status_code: int = 200, headers: Optional[Dict[str, str]] = None, media_type: Optional[str] = None) + - Base class for all responses. + - Parameters: + - content: The response body, as a string or bytes. + - status_code: The HTTP status code (e.g., 200, 404). + - headers: A dictionary of HTTP headers. + - media_type: The media type (Content-Type) of the response, e.g., "text/html", "application/json". + - FastAPI (Starlette) automatically adds Content-Length and Content-Type (with charset for text types). + +HTMLResponse: + __init__(content: Union[str, bytes], status_code: int = 200, headers: Optional[Dict[str, str]] = None) + - Returns an HTML response with 'text/html' media type. + - Inherits parameters from Response. + +PlainTextResponse: + __init__(content: Union[str, bytes], status_code: int = 200, headers: Optional[Dict[str, str]] = None) + - Returns a plain text response with 'text/plain' media type. + - Inherits parameters from Response. + +JSONResponse: + __init__(content: Any, status_code: int = 200, headers: Optional[Dict[str, str]] = None, media_type: Optional[str] = None) + - Returns a JSON response with 'application/json' media type. + - This is the default response type in FastAPI. + - 'content' is automatically serialized to JSON. + +ORJSONResponse: + __init__(content: Any, status_code: int = 200, headers: Optional[Dict[str, str]] = None, media_type: Optional[str] = None) + - A faster alternative to JSONResponse using the 'orjson' library. + - Requires 'orjson' to be installed (`pip install orjson`). + - Returns a JSON response with 'application/json' media type. + - 'content' is automatically serialized to JSON using orjson. +``` + +---------------------------------------- + +TITLE: Example JSON Data with Explicitly Set Default Values +DESCRIPTION: Illustrates a JSON data structure where fields that also have default values in the Pydantic model are explicitly provided. FastAPI and Pydantic are designed to include these in the response even if they match defaults, as they were explicitly set by the client or application logic. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/response-model.md#_snippet_15 + +LANGUAGE: JSON +CODE: +``` +{ + "name": "Bar", + "description": "The bartenders", + "price": 62, + "tax": 20.2 +} +``` + +---------------------------------------- + +TITLE: Import StaticFiles class from FastAPI +DESCRIPTION: This snippet demonstrates how to import the `StaticFiles` class from the `fastapi.staticfiles` module. This class is essential for configuring and serving static files like CSS, JavaScript, and images within a FastAPI application. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/staticfiles.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from fastapi.staticfiles import StaticFiles +``` + +---------------------------------------- + +TITLE: Install passlib for password hashing +DESCRIPTION: Installs the `passlib` library with bcrypt support, a comprehensive password hashing framework for Python. This library enables secure storage and verification of user passwords by supporting various hashing algorithms like bcrypt. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/security/oauth2-jwt.md#_snippet_1 + +LANGUAGE: console +CODE: +``` +pip install "passlib[bcrypt]" +``` + +---------------------------------------- + +TITLE: Returning a Dictionary with Item Price and ID +DESCRIPTION: This snippet shows the modified version of the return statement, where the item's price is returned instead of the item's name. This change demonstrates IDE auto-completion capabilities. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/index.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` + ... "item_price": item.price ... +``` + +---------------------------------------- + +TITLE: Calling Function with Explicit None for Optional-Typed Parameter +DESCRIPTION: Shows how to correctly call a function where a parameter is type-hinted as `Optional[str]` and is required. Passing `name=None` explicitly is valid, as `None` is allowed by the `Optional` type hint. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_8 + +LANGUAGE: Python +CODE: +``` +say_hi(name=None) # This works, None is valid 🎉 +``` + +---------------------------------------- + +TITLE: Declare Pydantic Model Example using schema_extra +DESCRIPTION: This method allows adding an example to a Pydantic model's JSON Schema using the `Config` class and `schema_extra` dictionary. This information is then used in the API documentation generated by FastAPI. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/schema-extra-example.md#_snippet_0 + +LANGUAGE: Python +CODE: +``` +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + class Config: + schema_extra = { + "example": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2 + } + } +``` + +---------------------------------------- + +TITLE: FastAPI: Reusing Annotated Dependencies in Path Operations +DESCRIPTION: This example showcases the benefits of using `Annotated` for dependencies in FastAPI. After defining `CurrentUser` with `Annotated`, it can be directly used as a type hint in path operation functions, eliminating code duplication and maintaining full type information for editor support and runtime validation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_17 + +LANGUAGE: Python +CODE: +``` +CurrentUser = Annotated[User, Depends(get_current_user)] + + +@app.get("/items/") +def read_items(user: CurrentUser): + ... + + +@app.post("/items/") +def create_item(user: CurrentUser, item: Item): + ... + + +@app.get("/items/{item_id}") +def read_item(user: CurrentUser, item_id: int): + ... + + +@app.delete("/items/{item_id}") +def delete_item(user: CurrentUser, item_id: int): + ... +``` + +---------------------------------------- + +TITLE: Accessing Dependency Values in Cleanup +DESCRIPTION: Shows how a dependency can access the value yielded by another dependency in its cleanup code, ensuring that necessary data is available for cleanup operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_5 + +LANGUAGE: Python +CODE: +``` +async def dependency_b(dep_a=Depends(dependency_a)): + dep_b = generate_dep_b() + try: + yield dep_b + finally: + await perform_cleanup_dep_b(dep_b, dep_a) +``` + +---------------------------------------- + +TITLE: Example JSON response for typed path parameter +DESCRIPTION: Displays the JSON output when a typed path parameter is used, demonstrating that FastAPI automatically converts the input string from the URL to the specified Python type (e.g., integer) before passing it to the function. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/path-params.md#_snippet_3 + +LANGUAGE: JSON +CODE: +``` +{"item_id":3} +``` + +---------------------------------------- + +TITLE: Python 3.10 Union type annotation vs. value +DESCRIPTION: This snippet illustrates the Python 3.10 vertical bar `|` syntax for type annotations. It highlights that while `PlaneItem | CarItem` works for type hints, it cannot be directly assigned as a value to parameters like `response_model` in FastAPI, where `typing.Union` must still be used to avoid invalid operations. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/extra-models.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +some_variable: PlaneItem | CarItem +``` + +---------------------------------------- + +TITLE: Using the Base Response Class in FastAPI +DESCRIPTION: Demonstrates the fundamental usage of the `Response` base class in FastAPI. It allows setting custom content, status code, headers, and media type, providing granular control over the HTTP response. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/advanced/custom-response.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI, Response + +app = FastAPI() + +@app.get("/custom-response") +async def custom_response(): + content = "This is a custom response." + return Response(content=content, media_type="text/plain", status_code=200) +``` + +---------------------------------------- + +TITLE: FastAPI: Importing Query and Setting Max Length +DESCRIPTION: Shows how to import `Query` from `fastapi` to enable advanced parameter validation. This example demonstrates setting a `max_length` constraint of 50 characters for the optional query parameter `q`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/query-params-str-validations.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items(q: Optional[str] = Query(default=None, max_length=50)): + results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} + if q: + results.update({"q": q}) + return results +``` + +---------------------------------------- + +TITLE: Nested Dependencies with yield +DESCRIPTION: Demonstrates how to use nested dependencies with `yield` in FastAPI, ensuring that exit code in each dependency is executed in the correct order. `dependency_c` depends on `dependency_b`, and `dependency_b` depends on `dependency_a`, and all of them use `yield`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +async def dependency_a() -> str: + yield "A" + + +async def dependency_b(dep_a: str = Depends(dependency_a)) -> str: + yield f"B {dep_a}" + + +async def dependency_c(dep_b: str = Depends(dependency_b)) -> str: + yield f"C {dep_b}" +``` + +---------------------------------------- + +TITLE: Pydantic Model Usage Example +DESCRIPTION: This code shows how to create and use a Pydantic model in Python. It demonstrates initializing a model with keyword arguments and from a dictionary using the ** operator. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/features.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +my_user: User = User(id=3, name="John Doe", joined="2018-07-19") + +second_user_data = { + "id": 4, + "name": "Mary", + "joined": "2018-11-30", +} + +my_second_user: User = User(**second_user_data) +``` + +---------------------------------------- + +TITLE: Define Custom String Validator with Pydantic AfterValidator +DESCRIPTION: Demonstrates how to create a custom validation function using Pydantic's `AfterValidator` and `Annotated` for FastAPI query parameters. This validator checks if a string value starts with one of several predefined prefixes (e.g., 'isbn-' or 'imdb-'), raising a `ValueError` if the condition is not met. This allows for advanced data validation beyond standard type checks. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/query-params-str-validations.md#_snippet_15 + +LANGUAGE: Python +CODE: +``` +from typing import Annotated +from pydantic import AfterValidator + +def validate_item_id(v: str) -> str: + if not v.startswith(("isbn-", "imdb-")): + raise ValueError("Item ID must start with 'isbn-' or 'imdb-'") + return v + +ItemId = Annotated[str, AfterValidator(validate_item_id)] +``` + +---------------------------------------- + +TITLE: JSON Response Example +DESCRIPTION: This JSON snippet shows the expected response from the `/users/me/` endpoint after successful authentication. It includes user details such as username, email, full name, and disabled status. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ja/docs/tutorial/security/oauth2-jwt.md#_snippet_6 + +LANGUAGE: JSON +CODE: +``` +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + +---------------------------------------- + +TITLE: Import Form from FastAPI +DESCRIPTION: To declare form parameters in FastAPI, import the `Form` class from the `fastapi` module. This allows FastAPI to correctly interpret and parse form-encoded data from incoming requests. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/request-forms.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI, Form +``` + +---------------------------------------- + +TITLE: Python Dict Unpacking Example +DESCRIPTION: Illustrates how to unpack dictionaries in Python to merge them. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/advanced/additional-responses.md#_snippet_6 + +LANGUAGE: Python +CODE: +``` +old_dict = { + "old key": "old value", + "second old key": "second old value", +} +new_dict = {**old_dict, "new key": "new value"} +``` + +LANGUAGE: Python +CODE: +``` +{ + "old key": "old value", + "second old key": "second old value", + "new key": "new value", +} +``` + +---------------------------------------- + +TITLE: Hero Table Model Definition +DESCRIPTION: Defines the `Hero` table model with fields like `id` and `secret_name`, inheriting from `HeroBase`. This model represents the complete structure of the Hero table in the database. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_snippet_10 + +LANGUAGE: Python +CODE: +``` +class Hero(HeroBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + secret_name: str +``` + +---------------------------------------- + +TITLE: Example User Profile API Response +DESCRIPTION: An example JSON response body for the `/users/me/` endpoint in a FastAPI application, illustrating the typical structure of a user profile object returned after successful authentication and data retrieval. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/oauth2-jwt.md#_snippet_4 + +LANGUAGE: JSON +CODE: +``` +{ + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false +} +``` + +---------------------------------------- + +TITLE: FastAPI Request Body Type Hint for List of Pydantic Models +DESCRIPTION: This Python snippet demonstrates how to declare a function parameter in FastAPI to accept a request body that is a JSON array (Python list) of Pydantic models. It uses the `typing.List` generic type for compatibility across Python versions. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/body-nested-models.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +images: List[Image] +``` + +---------------------------------------- + +TITLE: FastAPI User Profile Retrieval Endpoint (`GET /users/me`) +DESCRIPTION: Documents the `/users/me` API endpoint, which allows authenticated users to retrieve their own profile data. It details the successful response structure and common error scenarios for unauthenticated or inactive users, including HTTP status codes, `WWW-Authenticate` header, and error messages. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/security/simple-oauth2.md#_snippet_6 + +LANGUAGE: APIDOC +CODE: +``` +GET /users/me + Description: Retrieves the profile data for the currently authenticated user. + Authentication: Bearer Token (required) + Responses: + 200 OK: + Description: User data successfully retrieved. + Body: + { + "username": "johndoe", + "email": "johndoe@example.com", + "full_name": "John Doe", + "disabled": false, + "hashed_password": "fakehashedsecret" + } + 401 Unauthorized: + Description: User is not authenticated. + Body: + { + "detail": "Not authenticated" + } + Headers: + WWW-Authenticate: Bearer + 400 Bad Request: + Description: User is authenticated but inactive. + Body: + { + "detail": "Inactive user" + } +``` + +---------------------------------------- + +TITLE: Import FastAPI Middleware Classes +DESCRIPTION: Demonstrates how to import various middleware classes provided by FastAPI, including CORSMiddleware, GZipMiddleware, HTTPSRedirectMiddleware, TrustedHostMiddleware, and WSGIMiddleware, for use in a FastAPI application. These imports are essential for configuring application-wide behaviors. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/reference/middleware.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi.middleware.cors import CORSMiddleware +``` + +LANGUAGE: python +CODE: +``` +from fastapi.middleware.gzip import GZipMiddleware +``` + +LANGUAGE: python +CODE: +``` +from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware +``` + +LANGUAGE: python +CODE: +``` +from fastapi.middleware.trustedhost import TrustedHostMiddleware +``` + +LANGUAGE: python +CODE: +``` +from fastapi.middleware.wsgi import WSGIMiddleware +``` + +---------------------------------------- + +TITLE: Correcting Type Mismatch Using Type Hints +DESCRIPTION: Building on the previous example, this snippet provides the corrected version of the function. It demonstrates how to resolve a type error identified by static analysis by explicitly converting an integer to a string before concatenation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/python-types.md#_snippet_3 + +LANGUAGE: Python +CODE: +``` +{!../../docs_src/python_types/tutorial004.py!} +``` + +---------------------------------------- + +TITLE: FastAPI HTTP Path Operation Decorators +DESCRIPTION: This section lists the available HTTP method decorators in FastAPI for defining path operations. Each decorator corresponds to a specific HTTP verb and is used to associate a function with a particular URL path and method, allowing FastAPI to route incoming requests to the correct handler. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/em/docs/tutorial/first-steps.md#_snippet_6 + +LANGUAGE: APIDOC +CODE: +``` +@app.get(path: str) + - Defines a path operation that handles HTTP GET requests. +@app.post(path: str) + - Defines a path operation that handles HTTP POST requests. +@app.put(path: str) + - Defines a path operation that handles HTTP PUT requests. +@app.delete(path: str) + - Defines a path operation that handles HTTP DELETE requests. +@app.options(path: str) + - Defines a path operation that handles HTTP OPTIONS requests. +@app.head(path: str) + - Defines a path operation that handles HTTP HEAD requests. +@app.patch(path: str) + - Defines a path operation that handles HTTP PATCH requests. +@app.trace(path: str) + - Defines a path operation that handles HTTP TRACE requests. +``` + +---------------------------------------- + +TITLE: FastAPI: Declare Form Fields with Pydantic Models +DESCRIPTION: Illustrates how to declare form fields using Pydantic models in FastAPI. This allows for automatic data validation, parsing, and documentation of form data, simplifying the handling of complex form submissions and ensuring type safety. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/release-notes.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from typing import Annotated + +from fastapi import FastAPI, Form +from pydantic import BaseModel + +app = FastAPI() + + +class FormData(BaseModel): + username: str + password: str + + +@app.post("/login/") +async def login(data: Annotated[FormData, Form()]): + return data +``` + +---------------------------------------- + +TITLE: Query Parameter with Title and Description +DESCRIPTION: This snippet adds both a title and a description to the query parameter using the `title` and `description` parameters of the `Query` class. These metadata elements are included in the generated OpenAPI documentation. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ko/docs/tutorial/query-params-str-validations.md#_snippet_11 + +LANGUAGE: Python +CODE: +``` +from typing import Optional + +from fastapi import FastAPI, Query + +app = FastAPI() + + +@app.get("/items/") +async def read_items( + q: Optional[str] = Query( + None, title="Query string", description="Query description" + ) +): + return {"q": q} +``` + +---------------------------------------- + +TITLE: Compare Enum Members +DESCRIPTION: This example shows how to compare the path parameter (which is an Enum member) with the Enum members defined in the `ModelName` Enum. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/tutorial/path-params.md#_snippet_5 + +LANGUAGE: python +CODE: +``` + if model_name is ModelName.alexnet: + return {"model_name": model_name, "message": "Deep Learning FTW!"} +``` + +---------------------------------------- + +TITLE: Defining a Request Body with Pydantic and Handling PUT Requests +DESCRIPTION: This code extends the FastAPI application to include a Pydantic model (`Item`) for defining a request body. It also defines a PUT route ('/items/{item_id}') that accepts an `item_id` path parameter and an `Item` object in the request body. The `update_item` function then processes the received data. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/tr/docs/index.md#_snippet_4 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Modified Return Data +DESCRIPTION: This code snippet shows the modified version of the return data, where `item.name` is replaced with `item.price`. This change reflects a modification in the data being returned by the API. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh-hant/docs/index.md#_snippet_7 + +LANGUAGE: Python +CODE: +``` + ... "item_name": item.name ... +``` + +---------------------------------------- + +TITLE: Define GET Path Operation Decorator in FastAPI +DESCRIPTION: This code snippet shows how to define a path operation decorator using `@app.get("/")` to handle GET requests to the root path `/` in FastAPI. The decorator tells FastAPI that the function below it should handle requests to the specified path and HTTP method. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/first-steps.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +@app.get("/") +``` + +---------------------------------------- + +TITLE: Request Body and Path Parameters +DESCRIPTION: This code snippet shows how to combine request body parameters (using a Pydantic model) with path parameters in a FastAPI endpoint. FastAPI automatically distinguishes between them based on the function parameter type. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/body.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.put("/items/{item_id}") +async def create_item(item_id: int, item: Item): + return {"item_id": item_id, **item.dict()} +``` + +---------------------------------------- + +TITLE: Adding Generic ASGI Middleware in FastAPI +DESCRIPTION: Demonstrates the recommended way to add a generic ASGI middleware to a FastAPI application using `app.add_middleware()`. This method ensures proper integration, allowing internal middlewares to handle server errors and custom exception handlers correctly. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/middleware.md#_snippet_1 + +LANGUAGE: Python +CODE: +``` +from fastapi import FastAPI +from unicorn import UnicornMiddleware + +app = FastAPI() + +app.add_middleware(UnicornMiddleware, some_config="rainbow") +``` + +---------------------------------------- + +TITLE: Using Centralized Settings in FastAPI main.py +DESCRIPTION: Demonstrates how to import and use the pre-instantiated `settings` object from a `config.py` module within a FastAPI application's `main.py` file. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/advanced/settings.md#_snippet_8 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from .config import settings + +app = FastAPI() + +@app.get("/info") +async def info(): + return { + "app_name": settings.app_name, + "admin_email": settings.admin_email, + "items_per_user": settings.items_per_user, + } +``` + +---------------------------------------- + +TITLE: Updating Items with PUT Request and Pydantic Model +DESCRIPTION: This code snippet demonstrates how to handle a PUT request to update an item using a Pydantic model to define the request body. It defines an Item model with name, price, and is_offer fields, and uses it in the update_item function to receive and process the request body. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/fr/docs/index.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + price: float + is_offer: Union[bool, None] = None + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.put("/items/{item_id}") +def update_item(item_id: int, item: Item): + return {"item_name": item.name, "item_id": item_id} +``` + +---------------------------------------- + +TITLE: Import Starlette HTTPException for Handling +DESCRIPTION: Demonstrates how to import and rename Starlette's `HTTPException` to avoid naming conflicts with FastAPI's `HTTPException`, ensuring that exception handlers can catch both. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/tutorial/handling-errors.md#_snippet_14 + +LANGUAGE: Python +CODE: +``` +from starlette.exceptions import HTTPException as StarletteHTTPException +``` + +---------------------------------------- + +TITLE: Boolean Query Parameter Conversion in FastAPI (Python) +DESCRIPTION: This code snippet demonstrates how FastAPI automatically converts query parameters to boolean values. The `short` parameter is defined as a `bool`. + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/tr/docs/tutorial/query-params.md#_snippet_2 + +LANGUAGE: Python +CODE: +``` +from typing import Union + +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/items/{item_id}") +async def read_item(item_id: str, q: Union[str, None] = None, short: bool = False): + item = {"item_id": item_id} + if q: + item.update({"q": q}) + if not short: + item.update( + {"description": "This is an amazing item that has a long description"} + ) + return item +``` + +======================== +QUESTIONS AND ANSWERS +======================== +TOPIC: +Q: How can specialized Pydantic types, like `HttpUrl`, be utilized within FastAPI models? +A: Specialized Pydantic types, such as `HttpUrl`, can be used as field types within FastAPI models to enforce more specific validation rules beyond basic types like `str`. This ensures data integrity, like checking for valid URLs, and is reflected in the generated JSON Schema and OpenAPI documentation. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/zh/docs/tutorial/body-nested-models.md#_qa_4 + +---------------------------------------- + +TOPIC: +Q: What common fields are typically included in a `HeroBase` class? +A: A `HeroBase` class typically includes common fields that are shared across various hero-related models, such as `name` and `age`. This base class serves as a foundation for more specific hero models. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/ru/docs/tutorial/sql-databases.md#_qa_13 + +---------------------------------------- + +TOPIC: HTTP Basic Auth in FastAPI +Q: What is the purpose of the `WWW-Authenticate` header in the context of HTTP Basic Auth? +A: The `WWW-Authenticate` header, typically with a value of `Basic` and an optional `realm` parameter, instructs the browser to show an integrated prompt for a username and password. Once the user enters these details, the browser automatically sends them in the header for subsequent requests. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/security/http-basic-auth.md#_qa_1 + +---------------------------------------- + +TOPIC: HTTP Basic Auth in FastAPI +Q: How should a FastAPI application respond when HTTP Basic Auth credentials are found to be incorrect? +A: When HTTP Basic Auth credentials are incorrect, a FastAPI application should return an `HTTPException` with a status code of 401, indicating "Unauthorized." It is also crucial to include the `WWW-Authenticate` header in the response to prompt the browser to display the login dialog again for the user to re-enter credentials. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/security/http-basic-auth.md#_qa_5 + +---------------------------------------- + +TOPIC: +Q: How do you provide a single example for a request body using Body()? +A: To provide a single example for a request body, you pass the `examples` argument to `Body()`. This argument should be a dictionary where keys are example names and values are dictionaries containing the `value` of the example data. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/uk/docs/tutorial/schema-extra-example.md#_qa_6 + +---------------------------------------- + +TOPIC: HTTP Basic Auth in FastAPI +Q: What is HTTP Basic Auth and how does it function in a web application? +A: HTTP Basic Auth is an authentication method where the application expects a header containing a username and password. If these credentials are not provided, it returns an HTTP 401 "Unauthorized" error along with a `WWW-Authenticate` header. This header prompts the browser to display an integrated login dialog for the user to enter their credentials. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/en/docs/advanced/security/http-basic-auth.md#_qa_0 + +---------------------------------------- + +TOPIC: +Q: ¿Cuál es la práctica recomendada con respecto a example versus examples? +A: Se recomienda migrar del uso de la palabra clave singular `example` a `examples`. `examples` es parte del estándar de JSON Schema y es soportado por OpenAPI 3.1.0, mientras que `example` está obsoleto. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/schema-extra-example.md#_qa_8 + +---------------------------------------- + +TOPIC: +Q: ¿Qué funciones de FastAPI soportan la declaración de ejemplos para parámetros de request? +A: Funciones como `Path()`, `Query()`, `Header()`, `Cookie()`, `Body()`, `Form()` y `File()` soportan la declaración de un grupo de `examples` con información adicional que se añade a sus JSON Schemas dentro de OpenAPI. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/schema-extra-example.md#_qa_4 + +---------------------------------------- + +TOPIC: +Q: What are some key deployment concepts to consider beyond basic server execution? +A: Beyond basic server execution, key deployment concepts include security (HTTPS), ensuring the application runs on startup, handling restarts, replication (managing multiple processes), memory management, and executing necessary steps before the application starts. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/pt/docs/deployment/manually.md#_qa_6 + +---------------------------------------- + +TOPIC: +Q: ¿Se pueden declarar ejemplos usando Field() en modelos de Pydantic? +A: Sí, al usar `Field()` con modelos de Pydantic, también puedes declarar `examples` adicionales directamente dentro de la llamada a la función `Field()`. + + +SOURCE: https://github.com/tiangolo/fastapi/blob/master/docs/es/docs/tutorial/schema-extra-example.md#_qa_3 \ No newline at end of file diff --git a/aiprompts/ai_instruct/uppy/fastapi_mcp.md b/aiprompts/ai_instruct/uppy/fastapi_mcp.md new file mode 100644 index 00000000..1cd059be --- /dev/null +++ b/aiprompts/ai_instruct/uppy/fastapi_mcp.md @@ -0,0 +1,1544 @@ +======================== +CODE SNIPPETS +======================== +TITLE: MCP Client Configuration (SSE) +DESCRIPTION: Provides a JSON configuration example for connecting an MCP client to a FastAPI-MCP server using Server-Sent Events (SSE). This configuration specifies the URL of the MCP server. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/quickstart.mdx#_snippet_2 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "fastapi-mcp": { + "url": "http://localhost:8000/mcp" + } + } +} +``` + +---------------------------------------- + +TITLE: Basic Usage Example +DESCRIPTION: Demonstrates the fundamental integration of FastAPI-MCP into a FastAPI application. This example covers the initial setup and basic functionality. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/examples/README.md#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP + +app = FastAPI() +mcp = MCP(app) + +@app.get("/") +def read_root(): + return {"Hello": "World"} + +# To run this example: +# 1. Save the code as main.py +# 2. Install dependencies: pip install fastapi uvicorn fastapi-mcp +# 3. Run the server: uvicorn main:app --reload +``` + +---------------------------------------- + +TITLE: Install FastAPI-MCP with uv +DESCRIPTION: Installs the fastapi-mcp package using the uv package installer. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/installation.mdx#_snippet_0 + +LANGUAGE: bash +CODE: +``` +uv add fastapi-mcp +``` + +---------------------------------------- + +TITLE: Install FastAPI-MCP +DESCRIPTION: Instructions for installing the FastAPI-MCP package using uv or pip. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_0 + +LANGUAGE: bash +CODE: +``` +uv add fastapi-mcp +``` + +LANGUAGE: bash +CODE: +``` +pip install fastapi-mcp +``` + +---------------------------------------- + +TITLE: Install FastAPI-MCP with uv pip or pip +DESCRIPTION: Provides commands to install fastapi-mcp using uv pip or the standard pip installer. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/installation.mdx#_snippet_1 + +LANGUAGE: bash +CODE: +``` +uv pip install fastapi-mcp +``` + +LANGUAGE: bash +CODE: +``` +pip install fastapi-mcp +``` + +---------------------------------------- + +TITLE: Create Basic MCP Server +DESCRIPTION: Demonstrates how to create a basic MCP server by initializing a FastAPI app and wrapping it with the FastApiMCP class, then mounting the MCP to the application. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/quickstart.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +# Create (or import) a FastAPI app +app = FastAPI() + +# Create an MCP server based on this app +mcp = FastApiMCP(app) + +# Mount the MCP server directly to your app +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Set Up Development Environment with uv +DESCRIPTION: Installs project dependencies and sets up the virtual environment using the uv package manager. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_1 + +LANGUAGE: bash +CODE: +``` +uv sync +``` + +---------------------------------------- + +TITLE: Reregister Tools Example +DESCRIPTION: Illustrates how to re-register tools after adding new endpoints to an existing MCP server. This is necessary if endpoints are added after the initial server setup. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/FAQ.mdx#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP + +app = FastAPI() +mcp = MCP(app=app) + +# Add initial endpoints... + +# Add a new endpoint after MCP server creation +@app.get("/new_endpoint") +def new_endpoint(): + return {"message": "This is a new endpoint"} + +# Re-register all tools to include the new endpoint +mcp.setup_server() + +# Your FastAPI application setup... +``` + +---------------------------------------- + +TITLE: Install fastapi-mcp +DESCRIPTION: Installs the fastapi-mcp package using uv, a fast Python package installer. Alternatively, pip can be used. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README.md#_snippet_0 + +LANGUAGE: bash +CODE: +``` +uv add fastapi-mcp +``` + +LANGUAGE: bash +CODE: +``` +pip install fastapi-mcp +``` + +---------------------------------------- + +TITLE: Custom MCP Router Example +DESCRIPTION: Demonstrates advanced routing configuration in FastAPI-MCP, allowing for custom router setups beyond the default behavior. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/examples/README.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI, APIRouter +from fastapi_mcp import MCP + +app = FastAPI() + +# Create a custom router +custom_router = APIRouter() + +@custom_router.get("/custom_route") +def custom_route_endpoint(): + return {"message": "Custom Route Endpoint"} + +# Initialize MCP with the custom router +mcp = MCP(app, router=custom_router) + +# Endpoints defined directly on the app or other routers will also be discoverable by MCP if expose_all is True or they are explicitly exposed. +``` + +---------------------------------------- + +TITLE: Reregister Tools Example +DESCRIPTION: Shows how to add or register new endpoints (tools) to an existing MCP server after its initial creation. This allows for dynamic endpoint management. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/examples/README.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP + +app = FastAPI() +mcp = MCP(app) + +@app.get("/initial") +def initial_endpoint(): + return {"message": "Initial Endpoint"} + +@app.get("/added_later") +def added_later_endpoint(): + return {"message": "Added Later Endpoint"} + +# Register the second endpoint after MCP initialization +mcp.reregister("/added_later") + +# Now both /initial and /added_later endpoints are managed by MCP. +``` + +---------------------------------------- + +TITLE: Separate Server Example +DESCRIPTION: Demonstrates deploying the MCP server separately from the main FastAPI application. This pattern is useful for microservice architectures. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/examples/README.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP + +# Main FastAPI App +main_app = FastAPI() + +@main_app.get("/main") +def read_main(): + return {"message": "Main App"} + +# MCP Server App +mcp_app = FastAPI() +mcp = MCP(mcp_app) + +@mcp.get("/mcp_service") +def mcp_service(): + return {"message": "MCP Service"} + +# To run: +# 1. Run the main app: uvicorn main_app:main_app --port 8000 +# 2. Run the MCP app: uvicorn mcp_app:mcp_app --port 8001 +# The MCP server at port 8001 will discover and serve endpoints from the main app at port 8000. +``` + +---------------------------------------- + +TITLE: Full Schema Description Example +DESCRIPTION: Illustrates how to customize schema descriptions within FastAPI-MCP. This allows for more detailed and user-friendly API documentation. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/examples/README.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP +from pydantic import BaseModel + +app = FastAPI() +mcp = MCP(app) + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + class Config: + schema_extra = { + "example": { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 4.2 + } + } + +@app.post("/items/") +def create_item(item: Item): + return item + +# This example shows how to add custom descriptions to your Pydantic models, +# which will be reflected in the OpenAPI (Swagger UI) documentation. +``` + +---------------------------------------- + +TITLE: Create and Mount MCP Server +DESCRIPTION: Demonstrates the minimal code required to initialize FastAPI-MCP and mount the MCP server to a FastAPI application. This setup exposes your FastAPI endpoints as MCP tools. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/welcome.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP(app) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Run MCP Server with Uvicorn +DESCRIPTION: Shows how to run the FastAPI application with an integrated MCP server using uvicorn. This includes the necessary imports and the uvicorn.run call within an if __name__ == "__main__" block. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/quickstart.mdx#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP(app) +mcp.mount_http() + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) +``` + +---------------------------------------- + +TITLE: Install and Run Pre-commit Hooks +DESCRIPTION: Installs pre-commit hooks to automatically check code style and quality before commits, and then runs them. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_2 + +LANGUAGE: bash +CODE: +``` +uv run pre-commit install +uv run pre-commit run +``` + +---------------------------------------- + +TITLE: FastAPI MCP AuthConfig with Auth0 +DESCRIPTION: Example of initializing FastAPI MCP with Auth0 as the OAuth provider. It includes essential configuration like issuer, URLs, client credentials, scopes, and proxy setup. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_6 + +LANGUAGE: python +CODE: +``` +from fastapi_mcp import FastApiMCP, AuthConfig +from fastapi import Depends + +# Assume verify_auth is defined elsewhere +def verify_auth(): + pass + +mcp = FastApiMCP( + app, + auth_config=AuthConfig( + issuer="https://auth.example.com", + authorize_url="https://auth.example.com/authorize", + oauth_metadata_url="https://auth.example.com/.well-known/oauth-authorization-server", + client_id="your-client-id", + client_secret="your-client-secret", + audience="your-api-audience", + default_scope="openid profile email", + dependencies=[Depends(verify_auth)], + setup_proxies=True, + ), +) +``` + +---------------------------------------- + +TITLE: MCP Client Configuration (mcp-remote) +DESCRIPTION: Illustrates a JSON configuration for connecting an MCP client using the mcp-remote bridge, which is recommended for authentication or clients not supporting SSE. It includes the command, arguments, and an optional port for OAuth. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/quickstart.mdx#_snippet_3 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "fastapi-mcp": { + "command": "npx", + "args": [ + "mcp-remote", + "http://localhost:8000/mcp", + "8080" // Optional port number. Necessary if you want your OAuth to work and you don't have dynamic client registration. + ] + } + } +} +``` + +---------------------------------------- + +TITLE: Configure HTTP Timeout Example +DESCRIPTION: Illustrates how to customize the HTTP timeout settings for requests made by FastAPI-MCP when interacting with other services. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/examples/README.md#_snippet_6 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP + +app = FastAPI() + +# Configure MCP with a custom HTTP timeout (e.g., 10 seconds) +mcp = MCP(app, http_timeout=10.0) + +# Any requests made by MCP to other services will now use a 10-second timeout. +# This is useful for controlling how long MCP waits for responses from external APIs. +``` + +---------------------------------------- + +TITLE: MCP Inspector Usage +DESCRIPTION: Provides instructions on how to use the MCP Inspector to test a running FastAPI MCP server. It covers starting the inspector, connecting to the server, listing tools, and running an endpoint. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/FAQ.mdx#_snippet_2 + +LANGUAGE: bash +CODE: +``` +# Install the MCP Inspector globally (if not already installed) +npm install -g @modelcontextprotocol/inspector + +# Start the MCP Inspector +npx @modelcontextprotocol/inspector + +# In the inspector, connect to your MCP server +# Enter the mount path URL, e.g., http://127.0.0.1:8000/mcp + +# Navigate to the 'Tools' section +# Click 'List Tools' to see available endpoints + +# To test an endpoint: +# 1. Select a tool +# 2. Fill in required parameters +# 3. Click 'Run Tool' +``` + +---------------------------------------- + +TITLE: Advanced Usage: Separate Deployment +DESCRIPTION: Provides an example of deploying the MCP server separately from the main FastAPI application, showing how to create an MCP server from one app and mount it onto another. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_5 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +# Your API application +api_app = FastAPI() +# ... define your API endpoints on api_app ... + +# A separate MCP server application +mcp_app = FastAPI() + +# Create MCP server from the API app +mcp = FastApiMCP(api_app) + +# Mount the MCP server onto the separate application +mcp.mount(mcp_app) + +# Now you can run both applications separately: +# uvicorn main:api_app --host api-host --port 8001 +# uvicorn main:mcp_app --host mcp-host --port 8000 +``` + +---------------------------------------- + +TITLE: Running Separate FastAPI and MCP Apps +DESCRIPTION: Provides the bash commands to run the original API application and the mounted MCP server application concurrently using uvicorn. This setup allows for distinct hosting and port configurations. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/deploy.mdx#_snippet_1 + +LANGUAGE: bash +CODE: +``` +uvicorn main:api_app --host api-host --port 8001 +uvicorn main:mcp_app --host mcp-host --port 8000 +``` + +---------------------------------------- + +TITLE: Custom Exposed Endpoints Example +DESCRIPTION: Shows how to control which endpoints are exposed by FastAPI-MCP. This is useful for managing the visibility of specific API operations. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/examples/README.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP + +app = FastAPI() +mcp = MCP(app, expose_all=False) # Set expose_all to False to control exposure manually + +@app.get("/public") +def public_endpoint(): + return {"message": "This is public"} + +@app.get("/private") +def private_endpoint(): + return {"message": "This is private"} + +mcp.expose("/public") # Explicitly expose the public endpoint + +# The /private endpoint will not be exposed by MCP unless explicitly added. +``` + +---------------------------------------- + +TITLE: FastAPI Route Operation ID Examples +DESCRIPTION: Demonstrates how to use explicit operation_id in FastAPI routes for clearer tool naming in FastAPI-MCP. The first example shows an auto-generated operation_id, while the second shows an explicitly defined 'get_user_info' operation_id. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/tool-naming.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +import fastapi + +app = fastapi.FastAPI() + +# Auto-generated operation_id (something like "read_user_users__user_id__get") +@app.get("/users/{user_id}") +async def read_user(user_id: int): + return {"user_id": user_id} + +# Explicit operation_id (tool will be named "get_user_info") +@app.get("/users/{user_id}", operation_id="get_user_info") +async def read_user(user_id: int): + return {"user_id": user_id} +``` + +---------------------------------------- + +TITLE: Configure HTTP Timeout Example +DESCRIPTION: Demonstrates how to configure custom HTTP request timeouts by injecting an httpx client. This is useful for API endpoints that require longer response times than the default 5 seconds. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/getting-started/FAQ.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import MCP +import httpx + +app = FastAPI() +mcp = MCP(app=app) + +# Configure a custom timeout (e.g., 10 seconds) +custom_client = httpx.Client(timeout=10.0) +mcp.add_client(custom_client) + +# Your FastAPI endpoints would go here... +``` + +---------------------------------------- + +TITLE: HTTP Transport Client Configuration +DESCRIPTION: Example JSON configuration for an MCP client connecting via HTTP transport. It specifies the URL for the FastAPI-MCP server. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/transport.mdx#_snippet_3 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "fastapi-mcp": { + "url": "http://localhost:8000/mcp" + } + } +} +``` + +---------------------------------------- + +TITLE: SSE Transport Client Configuration +DESCRIPTION: Example JSON configuration for an MCP client connecting via SSE transport. It specifies the URL for the FastAPI-MCP server, which will be accessed using Server-Sent Events. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/transport.mdx#_snippet_4 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "fastapi-mcp": { + "url": "http://localhost:8000/sse" + } + } +} +``` + +---------------------------------------- + +TITLE: MCP Client Call for OAuth Flow +DESCRIPTION: Example of how to call an MCP server configured with OAuth 2 flow using `mcp-remote`. It specifies the server address and an optional port number, which is crucial for dynamic client registration. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_3 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "fastapi-mcp": { + "command": "npx", + "args": [ + "mcp-remote", + "http://localhost:8000/mcp", + "8080" // Optional port number. Necessary if you want your OAuth to work and you don't have dynamic client registration. + ] + } + } +} +``` + +---------------------------------------- + +TITLE: Running Commands with Virtual Environment Activated +DESCRIPTION: Demonstrates how to run project commands like pytest, mypy, and ruff after activating the virtual environment. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_3 + +LANGUAGE: bash +CODE: +``` +source .venv/bin/activate # On Windows: .venv\Scripts\activate + +# Then run commands directly +pytest +mypy . +ruff check . +``` + +---------------------------------------- + +TITLE: Development Workflow Steps +DESCRIPTION: Outlines the typical development process, including forking, branching, making changes, testing, formatting, and creating a pull request. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_8 + +LANGUAGE: bash +CODE: +``` +# Fork the repository and set the upstream remote +# Create a feature branch (git checkout -b feature/amazing-feature) +# Make your changes +# Run type checking (mypy .) +# Run the tests (pytest) +# Format your code (ruff check . and ruff format .). Not needed if pre-commit is installed, as it will run it for you. +# Commit your changes (git commit -m 'Add some amazing feature') +# Push to the branch (git push origin feature/amazing-feature) +# Open a Pull Request. Make sure the Pull Request's base branch is [the original repository's](https://github.com/tadata-org/fastapi_mcp/) `main` branch. +``` + +---------------------------------------- + +TITLE: Basic Usage of FastAPI-MCP +DESCRIPTION: Demonstrates the simplest way to integrate FastAPI-MCP into a FastAPI application by mounting the MCP server directly. The MCP server will be available at '/mcp'. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP(app) + +# Mount the MCP server directly to your FastAPI app +mcp.mount() +``` + +---------------------------------------- + +TITLE: Clone Repository and Set Up Remotes +DESCRIPTION: Clones the FastAPI-MCP repository and adds the upstream remote for tracking changes from the original repository. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_0 + +LANGUAGE: bash +CODE: +``` +git clone https://github.com/YOUR-USERNAME/fastapi_mcp.git +cd fastapi_mcp + +# Add the upstream remote +git remote add upstream https://github.com/tadata-org/fastapi_mcp.git +``` + +---------------------------------------- + +TITLE: Basic Usage: Mount MCP Server +DESCRIPTION: Demonstrates the simplest way to integrate FastAPI-MCP by mounting the MCP server directly into a FastAPI application. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP(app) + +# Directly mount the MCP server to your FastAPI application +mcp.mount() +``` + +---------------------------------------- + +TITLE: Mounting MCP to a Separate FastAPI App +DESCRIPTION: Demonstrates how to create an MCP server from an existing FastAPI application and mount it to a completely separate FastAPI application. This allows for independent deployment and management of the MCP server. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/deploy.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +# Your API app +api_app = FastAPI() +# ... define your API endpoints on api_app ... + +# A separate app for the MCP server +mcp_app = FastAPI() + +# Create MCP server from the API app +mcp = FastApiMCP(api_app) + +# Mount the MCP server to the separate app +mcp.mount_http(mcp_app) +``` + +---------------------------------------- + +TITLE: Adding Runtime Dependencies +DESCRIPTION: Adds a new package as a runtime dependency for the application using uv. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_5 + +LANGUAGE: bash +CODE: +``` +uv add new-package +``` + +---------------------------------------- + +TITLE: Custom HTTP Client with FastAPI-MCP +DESCRIPTION: Demonstrates how to initialize FastApiMCP with a custom httpx.AsyncClient, specifying a base URL and timeout. This allows for more control over HTTP requests made by the MCP integration. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_8 + +LANGUAGE: python +CODE: +``` +import httpx +from fastapi_mcp import FastApiMCP + +# Assuming 'app' is your FastAPI instance +# app = FastAPI() + +custom_client = httpx.AsyncClient( + base_url="https://api.example.com", + timeout=30.0 +) + +mcp = FastApiMCP( + app, + http_client=custom_client +) + +mcp.mount() +``` + +---------------------------------------- + +TITLE: Advanced Usage: Adding Endpoints After Server Creation +DESCRIPTION: Explains the process of refreshing the MCP server to include new endpoints that are added to the FastAPI application after the MCP server has already been initialized and mounted. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_6 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() +# ... define initial endpoints ... + +# Create MCP server +mcp = FastApiMCP(app) +mcp.mount() + +# Add new endpoints after MCP server creation +@app.get("/new/endpoint/", operation_id="new_endpoint") +async def new_endpoint(): + return {"message": "Hello, world!"} + +# Refresh the MCP server to include the new endpoints +mcp.setup_server() +``` + +---------------------------------------- + +TITLE: Running Commands without Activating Virtual Environment +DESCRIPTION: Shows how to execute project commands using the 'uv run' prefix when the virtual environment is not activated. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_4 + +LANGUAGE: bash +CODE: +``` +# Use uv run prefix for all commands +uv run pytest +uv run mypy . +uv run ruff check . +``` + +---------------------------------------- + +TITLE: Adding Development Dependencies +DESCRIPTION: Adds a new package specifically for development, testing, or CI purposes using uv with the --group dev flag. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_6 + +LANGUAGE: bash +CODE: +``` +uv add --group dev new-package +``` + +---------------------------------------- + +TITLE: Custom HTTP Client Configuration +DESCRIPTION: Demonstrates how to initialize FastAPI-MCP with a custom httpx.AsyncClient, allowing for a specified base URL and timeout settings. This is useful when the default ASGI transport is not suitable or when explicit HTTP client configuration is required. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/asgi.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +import httpx +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +custom_client = httpx.AsyncClient( + base_url="https://api.example.com", + timeout=30.0 +) + +mcp = FastApiMCP( + app, + http_client=custom_client +) + +mcp.mount() +``` + +---------------------------------------- + +TITLE: Running Tests +DESCRIPTION: Command to execute all tests in the project using pytest. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_10 + +LANGUAGE: bash +CODE: +``` +# Run all tests +pytest +``` + +---------------------------------------- + +TITLE: Run Tests +DESCRIPTION: Command to execute all tests using the pytest framework. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_11 + +LANGUAGE: bash +CODE: +``` +# Run all tests +pytest +``` + +---------------------------------------- + +TITLE: MCP Server Configuration for Claude Desktop (Windows) +DESCRIPTION: Provides the JSON configuration for Claude Desktop to connect to an MCP server via mcp-proxy on Windows. It specifies the command to run and arguments, including the MCP server URL. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_9 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "my-api-mcp-proxy": { + "command": "mcp-proxy", + "args": ["http://127.0.0.1:8000/mcp"] + } + } +} +``` + +---------------------------------------- + +TITLE: Code Quality Checks +DESCRIPTION: Commands to check code formatting, style, and type correctness using ruff and mypy. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_9 + +LANGUAGE: bash +CODE: +``` +# Check code formatting and style +ruff check . +ruff format . + +# Check types +mypy . +``` + +---------------------------------------- + +TITLE: Communication: Using Custom httpx.AsyncClient +DESCRIPTION: Demonstrates how to provide a custom `httpx.AsyncClient` to FastAPI-MCP for specifying a custom base URL or using different transport methods for communication with the FastAPI application. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_7 + +LANGUAGE: python +CODE: +``` +import httpx +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +# Example of providing a custom httpx.AsyncClient +# custom_client = httpx.AsyncClient(base_url="http://localhost:8000") +# mcp = FastApiMCP(app, client=custom_client) +# mcp.mount() +``` + +---------------------------------------- + +TITLE: Advanced Usage: Custom Schema Descriptions +DESCRIPTION: Shows how to customize the MCP server's behavior regarding response descriptions, including options to include all possible response schemas and full response schemas in tool descriptions. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_3 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + name="My API MCP", + describe_all_responses=True, # Include all possible response schemas in tool descriptions + describe_full_response_schema=True # Include full JSON schemas in tool descriptions +) + +mcp.mount() +``` + +---------------------------------------- + +TITLE: Control Tool and Schema Descriptions +DESCRIPTION: Configures the MCP server to include all possible response schemas in tool descriptions using 'describe_all_responses' or full JSON schema using 'describe_full_response_schema'. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/customization.mdx#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + name="My API MCP", + description="Very cool MCP server", + describe_all_responses=True, + describe_full_response_schema=True +) + +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: MCP Server Configuration for Claude Desktop (MacOS) +DESCRIPTION: Provides the JSON configuration for Claude Desktop to connect to an MCP server via mcp-proxy on MacOS. It requires the full path to the mcp-proxy executable and the MCP server URL. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_10 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "my-api-mcp-proxy": { + "command": "/Full/Path/To/Your/Executable/mcp-proxy", + "args": ["http://127.0.0.1:8000/mcp"] + } + } +} +``` + +---------------------------------------- + +TITLE: Set Server Metadata +DESCRIPTION: Defines the MCP server name and description by passing 'name' and 'description' arguments to the FastApiMCP constructor. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/customization.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + name="My API MCP", + description="Very cool MCP server", +) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Committing Dependency Changes +DESCRIPTION: Commits the updated pyproject.toml and uv.lock files after adding new dependencies. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/CONTRIBUTING.md#_snippet_7 + +LANGUAGE: bash +CODE: +``` +git add pyproject.toml uv.lock +git commit -m "Add new-package dependency" +``` + +---------------------------------------- + +TITLE: Auth0 Environment Variables +DESCRIPTION: Required environment variables for Auth0 integration with FastAPI MCP. These variables store your Auth0 tenant and client credentials. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_5 + +LANGUAGE: env +CODE: +``` +AUTH0_DOMAIN=your-tenant.auth0.com +AUTH0_AUDIENCE=https://your-tenant.auth0.com/api/v2/ +AUTH0_CLIENT_ID=your-client-id +AUTH0_CLIENT_SECRET=your-client-secret +``` + +---------------------------------------- + +TITLE: Mount HTTP/SSE Transport with Custom Routing +DESCRIPTION: Demonstrates mounting both HTTP and SSE transports to custom paths using an APIRouter. This allows for more flexible routing and integration with existing FastAPI applications. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/transport.mdx#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI, APIRouter +from fastapi_mcp import FastApiMCP + +app = FastAPI() +router = APIRouter(prefix="/api/v1") + +mcp = FastApiMCP(app) + +# Mount to custom path with HTTP transport +mcp.mount_http(router, mount_path="/my-http") + +# Or with SSE transport +mcp.mount_sse(router, mount_path="/my-sse") +``` + +---------------------------------------- + +TITLE: Refresh FastAPI MCP Server with New Endpoints +DESCRIPTION: This snippet shows how to add a new endpoint to a FastAPI application and then refresh the FastAPI MCP server to recognize and serve the new endpoint. It assumes you have already initialized FastApiMCP and mounted the HTTP server. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/refresh.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP(app) +mcp.mount_http() + +# Add new endpoints after MCP server creation +@app.get("/new/endpoint/", operation_id="new_endpoint") +async def new_endpoint(): + return {"message": "Hello, world!"} + +# Refresh the MCP server to include the new endpoint +mcp.setup_server() +``` + +---------------------------------------- + +TITLE: Mount HTTP Transport +DESCRIPTION: Mounts the FastAPI application using HTTP transport, which is the recommended method for client-server communication. This leverages the latest MCP Streamable HTTP specification for better session management and connection handling. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/transport.mdx#_snippet_0 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() +mcp = FastApiMCP(app) + +# Mount using HTTP transport (recommended) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Advanced Usage: Customizing Exposed Endpoints +DESCRIPTION: Explains how to control which FastAPI endpoints are exposed as MCP tools using Open API operation IDs or tags, covering include/exclude patterns for both. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_4 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +# Include only specific operations +mcp = FastApiMCP( + app, + include_operations=["get_user", "create_user"] +) + +# Exclude specific operations +mcp = FastApiMCP( + app, + exclude_operations=["delete_user"] +) + +# Include only operations with specific tags +mcp = FastApiMCP( + app, + include_tags=["users", "public"] +) + +# Exclude operations with specific tags +mcp = FastApiMCP( + app, + exclude_tags=["admin", "internal"] +) + +# Combine operation ID and tag filtering (include mode) +mcp = FastApiMCP( + app, + include_operations=["user_login"], + include_tags=["public"] +) + +mcp.mount() +``` + +---------------------------------------- + +TITLE: mcp-remote Configuration for Fixed Port +DESCRIPTION: JSON configuration for mcp-remote to run on a fixed port, which is crucial for setting up OAuth provider callback URLs correctly. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_7 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "example": { + "command": "npx", + "args": [ + "mcp-remote", + "http://localhost:8000/mcp", + "8080" + ] + } + } +} +``` + +---------------------------------------- + +TITLE: Basic Token Passthrough Configuration +DESCRIPTION: Configures an MCP client to pass an Authorization header to FastAPI endpoints. This is useful for simple token-based authentication without a full OAuth flow. It specifies the command to run and environment variables for the token. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_0 + +LANGUAGE: json +CODE: +``` +{ + "mcpServers": { + "remote-example": { + "command": "npx", + "args": [ + "mcp-remote", + "http://localhost:8000/mcp", + "--header", + "Authorization:${AUTH_HEADER}" + ] + }, + "env": { + "AUTH_HEADER": "Bearer " + } + } +} +``` + +---------------------------------------- + +TITLE: Tool Naming: Explicit operation_id +DESCRIPTION: Illustrates how to use the `operation_id` parameter in FastAPI route definitions to provide clear and intuitive names for MCP tools, contrasting it with auto-generated IDs. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/README_zh-CN.md#_snippet_2 + +LANGUAGE: python +CODE: +``` +# Auto-generated operation_id (like "read_user_users__user_id__get") +@app.get("/users/{user_id}") +async def read_user(user_id: int): + return {"user_id": user_id} + +# Explicit operation_id (tool will be named "get_user_info") +@app.get("/users/{user_id}", operation_id="get_user_info") +async def read_user(user_id: int): + return {"user_id": user_id} +``` + +---------------------------------------- + +TITLE: FastAPI-MCP with Token Passthrough Dependency +DESCRIPTION: Sets up FastAPI-MCP to require an authorization header by adding a dependency to verify authentication. This ensures that only requests with a valid authorization header are processed. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import Depends +from fastapi_mcp import FastApiMCP, AuthConfig + +mcp = FastApiMCP( + app, + name="Protected MCP", + auth_config=AuthConfig( + dependencies=[Depends(verify_auth)], + ), +) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Include Specific Tags +DESCRIPTION: Exposes only FastAPI endpoints associated with the specified tags by providing a list of tags to the 'include_tags' argument. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/customization.mdx#_snippet_4 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + include_tags=["users", "public"] +) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: FastAPI-MCP with OAuth 2 Flow +DESCRIPTION: Configures FastAPI-MCP to support the full OAuth 2 flow, compliant with the MCP Spec. This includes specifying issuer, authorization URLs, client credentials, and an audience for secure authentication. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import Depends +from fastapi_mcp import FastApiMCP, AuthConfig + +mcp = FastApiMCP( + app, + name="MCP With OAuth", + auth_config=AuthConfig( + issuer=f"https://auth.example.com/", + authorize_url=f"https://auth.example.com/authorize", + oauth_metadata_url=f"https://auth.example.com/.well-known/oauth-authorization-server", + audience="my-audience", + client_id="my-client-id", + client_secret="my-client-secret", + dependencies=[Depends(verify_auth)], + setup_proxies=True, + ), +) + +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Mount SSE Transport +DESCRIPTION: Mounts the FastAPI application using SSE (Server-Sent Events) transport. This method is maintained for backwards compatibility with older MCP implementations. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/transport.mdx#_snippet_1 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() +mcp = FastApiMCP(app) + +# Mount using SSE transport (backwards compatibility) +mcp.mount_sse() +``` + +---------------------------------------- + +TITLE: Combine Operation and Tag Filtering +DESCRIPTION: Combines operation ID filtering with tag filtering to selectively expose FastAPI endpoints. Endpoints matching either criteria will be included. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/customization.mdx#_snippet_6 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + include_operations=["user_login"], + include_tags=["public"] +) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Include Specific Operations +DESCRIPTION: Exposes only the specified FastAPI endpoints by providing a list of operation IDs to the 'include_operations' argument. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/customization.mdx#_snippet_2 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + include_operations=["get_user", "create_user"] +) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: FastAPI-MCP with Custom OAuth Metadata +DESCRIPTION: Configures FastAPI-MCP to use custom OAuth metadata, providing full control over the OAuth flow. This is useful for integrating with existing MCP-compliant OAuth servers or specialized implementations. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/advanced/auth.mdx#_snippet_4 + +LANGUAGE: python +CODE: +``` +from fastapi import Depends +from fastapi_mcp import FastApiMCP, AuthConfig + +mcp = FastApiMCP( + app, + name="MCP With Custom OAuth", + auth_config=AuthConfig( + # Provide your own complete OAuth metadata + custom_oauth_metadata={ + "issuer": "https://auth.example.com", + "authorization_endpoint": "https://auth.example.com/authorize", + "token_endpoint": "https://auth.example.com/token", + "registration_endpoint": "https://auth.example.com/register", + "scopes_supported": ["openid", "profile", "email"], + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code"], + "token_endpoint_auth_methods_supported": ["none"], + "code_challenge_methods_supported": ["S256"] + }, + + # Your auth checking dependency + dependencies=[Depends(verify_auth)], + ), +) + +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Exclude Specific Tags +DESCRIPTION: Excludes FastAPI endpoints associated with the specified tags from being exposed as MCP tools by providing a list of tags to the 'exclude_tags' argument. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/customization.mdx#_snippet_5 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + exclude_tags=["admin", "internal"] +) +mcp.mount_http() +``` + +---------------------------------------- + +TITLE: Exclude Specific Operations +DESCRIPTION: Excludes specific FastAPI endpoints from being exposed as MCP tools by providing a list of operation IDs to the 'exclude_operations' argument. + +SOURCE: https://github.com/tadata-org/fastapi_mcp/blob/main/docs/configurations/customization.mdx#_snippet_3 + +LANGUAGE: python +CODE: +``` +from fastapi import FastAPI +from fastapi_mcp import FastApiMCP + +app = FastAPI() + +mcp = FastApiMCP( + app, + exclude_operations=["delete_user"] +) +mcp.mount_http() +``` \ No newline at end of file diff --git a/aiprompts/ai_instruct/uppy/tus.md b/aiprompts/ai_instruct/uppy/tus.md new file mode 100644 index 00000000..9736876f --- /dev/null +++ b/aiprompts/ai_instruct/uppy/tus.md @@ -0,0 +1,225 @@ +# tus Resumable Upload Protocol (Condensed for Coding Agents) + +## Core Protocol + +All Clients and Servers MUST implement the core protocol for resumable uploads. + +### Resuming an Upload + +1. **Determine Offset (HEAD Request):** + * **Request:** + ``` + HEAD /files/{upload_id} HTTP/1.1 + Host: tus.example.org + Tus-Resumable: 1.0.0 + ``` + * **Response:** + ``` + HTTP/1.1 200 OK + Upload-Offset: {current_offset} + Tus-Resumable: 1.0.0 + ``` + * Server MUST include `Upload-Offset`. + * Server MUST include `Upload-Length` if known. + * Server SHOULD return `200 OK` or `204 No Content`. + * Server MUST prevent caching: `Cache-Control: no-store`. + +2. **Resume Upload (PATCH Request):** + * **Request:** + ``` + PATCH /files/{upload_id} HTTP/1.1 + Host: tus.example.org + Content-Type: application/offset+octet-stream + Content-Length: {chunk_size} + Upload-Offset: {current_offset} + Tus-Resumable: 1.0.0 + + [binary data chunk] + ``` + * **Response:** + ``` + HTTP/1.1 204 No Content + Tus-Resumable: 1.0.0 + Upload-Offset: {new_offset} + ``` + * `Content-Type` MUST be `application/offset+octet-stream`. + * `Upload-Offset` in request MUST match server's current offset (else `409 Conflict`). + * Server MUST acknowledge with `204 No Content` and `Upload-Offset` (new offset). + * Server SHOULD return `404 Not Found` for non-existent resources. + +### Common Headers + +* **`Upload-Offset`**: Non-negative integer. Byte offset within resource. +* **`Upload-Length`**: Non-negative integer. Total size of upload in bytes. +* **`Tus-Version`**: Comma-separated list of supported protocol versions (Server response). +* **`Tus-Resumable`**: Protocol version used (e.g., `1.0.0`). MUST be in every request/response (except `OPTIONS`). If client version unsupported, server responds `412 Precondition Failed` with `Tus-Version`. +* **`Tus-Extension`**: Comma-separated list of supported extensions (Server response). Omitted if none. +* **`Tus-Max-Size`**: Non-negative integer. Max allowed upload size in bytes (Server response). +* **`X-HTTP-Method-Override`**: String. Client MAY use to override HTTP method (e.g., for `PATCH`/`DELETE` limitations). + +### Server Configuration (OPTIONS Request) + +* **Request:** + ``` + OPTIONS /files HTTP/1.1 + Host: tus.example.org + ``` +* **Response:** + ``` + HTTP/1.1 204 No Content + Tus-Resumable: 1.0.0 + Tus-Version: 1.0.0,0.2.2,0.2.1 + Tus-Max-Size: 1073741824 + Tus-Extension: creation,expiration + ``` + * Response MUST contain `Tus-Version`. MAY include `Tus-Extension` and `Tus-Max-Size`. + * Client SHOULD NOT include `Tus-Resumable` in request. + +## Protocol Extensions + +Clients SHOULD use `OPTIONS` request and `Tus-Extension` header for feature detection. + +### Creation (`creation` extension) + +Create a new upload resource. Server MUST add `creation` to `Tus-Extension`. + +* **Request (POST):** + ``` + POST /files HTTP/1.1 + Host: tus.example.org + Content-Length: 0 + Upload-Length: {total_size} OR Upload-Defer-Length: 1 + Tus-Resumable: 1.0.0 + Upload-Metadata: filename {base64_filename},is_confidential + ``` + * MUST include `Upload-Length` or `Upload-Defer-Length: 1`. + * If `Upload-Defer-Length: 1`, client MUST set `Upload-Length` in subsequent `PATCH`. + * `Upload-Length: 0` creates an immediately complete empty file. + * Client MAY supply `Upload-Metadata` (key-value pairs, value Base64 encoded). + * If `Upload-Length` exceeds `Tus-Max-Size`, server responds `413 Request Entity Too Large`. +* **Response:** + ``` + HTTP/1.1 201 Created + Location: {upload_url} + Tus-Resumable: 1.0.0 + ``` + * Server MUST respond `201 Created` and set `Location` header to new resource URL. + * New resource has implicit offset `0`. + +#### Headers + +* **`Upload-Defer-Length`**: `1`. Indicates upload size is unknown. Server adds `creation-defer-length` to `Tus-Extension` if supported. +* **`Upload-Metadata`**: Comma-separated `key value` pairs. Key: no spaces/commas, ASCII. Value: Base64 encoded. + +### Creation With Upload (`creation-with-upload` extension) + +Include initial upload data in the `POST` request. Server MUST add `creation-with-upload` to `Tus-Extension`. Depends on `creation` extension. + +* **Request (POST):** + ``` + POST /files HTTP/1.1 + Host: tus.example.org + Content-Length: {initial_chunk_size} + Upload-Length: {total_size} + Tus-Resumable: 1.0.0 + Content-Type: application/offset+octet-stream + Expect: 100-continue + + [initial binary data chunk] + ``` + * Similar rules as `PATCH` apply for content. + * Client SHOULD include `Expect: 100-continue`. +* **Response:** + ``` + HTTP/1.1 201 Created + Location: {upload_url} + Tus-Resumable: 1.0.0 + Upload-Offset: {accepted_offset} + ``` + * Server MUST include `Upload-Offset` with accepted bytes. + +### Expiration (`expiration` extension) + +Server MAY remove unfinished uploads. Server MUST add `expiration` to `Tus-Extension`. + +* **Response (PATCH/POST):** + ``` + HTTP/1.1 204 No Content + Upload-Expires: Wed, 25 Jun 2014 16:00:00 GMT + Tus-Resumable: 1.0.0 + Upload-Offset: {new_offset} + ``` +* **`Upload-Expires`**: Datetime in RFC 9110 format. Indicates when upload expires. Client SHOULD use to check validity. Server SHOULD respond `404 Not Found` or `410 Gone` for expired uploads. + +### Checksum (`checksum` extension) + +Verify data integrity of `PATCH` requests. Server MUST add `checksum` to `Tus-Extension`. Server MUST support `sha1`. + +* **Request (PATCH):** + ``` + PATCH /files/{upload_id} HTTP/1.1 + Content-Length: {chunk_size} + Upload-Offset: {current_offset} + Tus-Resumable: 1.0.0 + Upload-Checksum: {algorithm} {base64_checksum} + + [binary data chunk] + ``` +* **Response:** + * `204 No Content`: Checksums match. + * `400 Bad Request`: Algorithm not supported. + * `460 Checksum Mismatch`: Checksums mismatch. + * In `400`/`460` cases, chunk MUST be discarded, upload/offset NOT updated. +* **`Tus-Checksum-Algorithm`**: Comma-separated list of supported algorithms (Server response to `OPTIONS`). +* **`Upload-Checksum`**: `{algorithm} {Base64_encoded_checksum}`. + +### Termination (`termination` extension) + +Client can terminate uploads. Server MUST add `termination` to `Tus-Extension`. + +* **Request (DELETE):** + ``` + DELETE /files/{upload_id} HTTP/1.1 + Host: tus.example.org + Content-Length: 0 + Tus-Resumable: 1.0.0 + ``` +* **Response:** + ``` + HTTP/1.1 204 No Content + Tus-Resumable: 1.0.0 + ``` + * Server SHOULD free resources, MUST respond `204 No Content`. + * Future requests to URL SHOULD return `404 Not Found` or `410 Gone`. + +### Concatenation (`concatenation` extension) + +Concatenate multiple partial uploads into a single final upload. Server MUST add `concatenation` to `Tus-Extension`. + +* **Partial Upload Creation (POST):** + ``` + POST /files HTTP/1.1 + Upload-Concat: partial + Upload-Length: {partial_size} + Tus-Resumable: 1.0.0 + ``` + * `Upload-Concat: partial` header. + * Server SHOULD NOT process partial uploads until concatenated. +* **Final Upload Creation (POST):** + ``` + POST /files HTTP/1.1 + Upload-Concat: final;{url_partial1} {url_partial2} ... + Tus-Resumable: 1.0.0 + ``` + * `Upload-Concat: final;{space-separated_partial_urls}`. + * Client MUST NOT include `Upload-Length`. + * Final upload length is sum of partials. + * Server MAY delete partials after concatenation. + * Server MUST respond `403 Forbidden` to `PATCH` requests against final upload. +* **`concatenation-unfinished`**: Server adds to `Tus-Extension` if it supports concatenation while partial uploads are in progress. +* **HEAD Request for Final Upload:** + * Response SHOULD NOT contain `Upload-Offset` unless concatenation finished. + * After success, `Upload-Offset` and `Upload-Length` MUST be equal. + * Response MUST include `Upload-Concat` header. +* **HEAD Request for Partial Upload:** + * Response MUST contain `Upload-Offset`. \ No newline at end of file diff --git a/aiprompts/ai_instruct/uppy/tus_implementation.md b/aiprompts/ai_instruct/uppy/tus_implementation.md new file mode 100644 index 00000000..c2f41012 --- /dev/null +++ b/aiprompts/ai_instruct/uppy/tus_implementation.md @@ -0,0 +1,667 @@ + +# TUS (1.0.0) — Server-Side Specs (Concise) + +## Always + +* All requests/responses **except** `OPTIONS` MUST include: `Tus-Resumable: 1.0.0`. + If unsupported → `412 Precondition Failed` + `Tus-Version`. +* Canonical server features via `OPTIONS /files`: + + * `Tus-Version: 1.0.0` + * `Tus-Extension: creation,creation-with-upload,termination,checksum,concatenation,concatenation-unfinished` (as supported) + * `Tus-Max-Size: ` (if hard limit) + * `Tus-Checksum-Algorithm: sha1[,md5,crc32...]` (if checksum ext.) + +## Core + +* **Create:** `POST /files` with `Upload-Length: ` OR `Upload-Defer-Length: 1`. Optional `Upload-Metadata`. + + * `201 Created` + `Location: /files/{id}`, echo `Tus-Resumable`. + * *Creation-With-Upload:* If body present → `Content-Type: application/offset+octet-stream`, accept bytes, respond with `Upload-Offset`. +* **Status:** `HEAD /files/{id}` + + * Always return `Upload-Offset` for partial uploads, include `Upload-Length` if known; if deferred, return `Upload-Defer-Length: 1`. `Cache-Control: no-store`. +* **Upload:** `PATCH /files/{id}` + + * `Content-Type: application/offset+octet-stream` and `Upload-Offset` (must match server). + * On success → `204 No Content` + new `Upload-Offset`. + * Mismatch → `409 Conflict`. Bad type → `415 Unsupported Media Type`. +* **Terminate:** `DELETE /files/{id}` (if supported) → `204 No Content`. Subsequent requests → `404/410`. + +## Checksum (optional but implemented here) + +* Client MAY send: `Upload-Checksum: ` per `PATCH`. + + * Server MUST verify request body’s checksum of the exact received bytes. + * If algo unsupported → `400 Bad Request`. + * If mismatch → **discard the chunk** (no offset change) and respond `460 Checksum Mismatch`. + * If OK → `204 No Content` + new `Upload-Offset`. +* `OPTIONS` MUST include `Tus-Checksum-Algorithm` (comma-separated algos). + +## Concatenation (optional but implemented here) + +* **Partial uploads:** `POST /files` with `Upload-Concat: partial` and `Upload-Length`. (MUST have length; may use creation-with-upload/patch thereafter.) +* **Final upload:** `POST /files` with + `Upload-Concat: final; /files/{a} /files/{b} ...` + + * MUST NOT include `Upload-Length`. + * Final uploads **cannot** be `PATCH`ed (`403`). + * Server SHOULD assemble final (in order). + * If `concatenation-unfinished` supported, final may be created before partials completed; server completes once all partials are done. +* **HEAD semantics:** + + * For *partial*: MUST include `Upload-Offset`. + * For *final* before concatenation: SHOULD NOT include `Upload-Offset`. `Upload-Length` MAY be present if computable (= sum of partials’ lengths when known). + * After finalization: `Upload-Offset == Upload-Length`. + +--- + +# TUS FastAPI Server (disk-only, crash-safe, checksum + concatenation) + +**Features** + +* All persistent state on disk: + + ``` + TUS_ROOT/ + {upload_id}/ + info.json # canonical metadata & status + data.part # exists while uploading or while building final + data # final file after atomic rename + ``` +* Crash recovery: `HEAD` offset = size of `data.part` or `data`. +* `.part` during upload; `os.replace()` (atomic) to `data` on completion. +* Streaming I/O; `fsync` on file + parent directory. +* Checksum: supports `sha1` (can easily add md5/crc32). +* Concatenation: server builds final when partials complete; supports `concatenation-unfinished`. + +> Run with: `uv pip install fastapi uvicorn` then `uvicorn tus_server:app --host 0.0.0.0 --port 8080` (or `python tus_server.py`). +> Set `TUS_ROOT` env to choose storage root. + +```python +# tus_server.py +from fastapi import FastAPI, Request, Response, HTTPException +from typing import Optional, Dict, Any, List +import os, json, uuid, base64, asyncio, errno, hashlib + +# ----------------------------- +# Config +# ----------------------------- +TUS_VERSION = "1.0.0" +# Advertise extensions implemented below: +TUS_EXTENSIONS = ",".join([ + "creation", + "creation-with-upload", + "termination", + "checksum", + "concatenation", + "concatenation-unfinished", +]) +# Supported checksum algorithms (keys = header token) +CHECKSUM_ALGOS = ["sha1"] # add "md5" if desired + +TUS_ROOT = os.environ.get("TUS_ROOT", "/tmp/tus") +MAX_SIZE = 1 << 40 # 1 TiB default + +os.makedirs(TUS_ROOT, exist_ok=True) +app = FastAPI() + +# Per-process locks to prevent concurrent mutations on same upload_id +_locks: Dict[str, asyncio.Lock] = {} +def _lock_for(upload_id: str) -> asyncio.Lock: + if upload_id not in _locks: + _locks[upload_id] = asyncio.Lock() + return _locks[upload_id] + +# ----------------------------- +# Path helpers +# ----------------------------- +def upload_dir(upload_id: str) -> str: + return os.path.join(TUS_ROOT, upload_id) + +def info_path(upload_id: str) -> str: + return os.path.join(upload_dir(upload_id), "info.json") + +def part_path(upload_id: str) -> str: + return os.path.join(upload_dir(upload_id), "data.part") + +def final_path(upload_id: str) -> str: + return os.path.join(upload_dir(upload_id), "data") + +# ----------------------------- +# FS utils (crash-safe) +# ----------------------------- +def _fsync_dir(path: str) -> None: + fd = os.open(path, os.O_DIRECTORY) + try: + os.fsync(fd) + finally: + os.close(fd) + +def _write_json_atomic(path: str, obj: Dict[str, Any]) -> None: + tmp = f"{path}.tmp" + data = json.dumps(obj, separators=(",", ":"), ensure_ascii=False) + with open(tmp, "w", encoding="utf-8") as f: + f.write(data) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp, path) + _fsync_dir(os.path.dirname(path)) + +def _read_json(path: str) -> Dict[str, Any]: + with open(path, "r", encoding="utf-8") as f: + return json.load(f) + +def _size(path: str) -> int: + try: + return os.path.getsize(path) + except FileNotFoundError: + return 0 + +def _exists(path: str) -> bool: + return os.path.exists(path) + +# ----------------------------- +# TUS helpers +# ----------------------------- +def _ensure_tus_version(req: Request): + if req.method == "OPTIONS": + return + v = req.headers.get("Tus-Resumable") + if v is None: + raise HTTPException(status_code=412, detail="Missing Tus-Resumable") + if v != TUS_VERSION: + raise HTTPException(status_code=412, detail="Unsupported Tus-Resumable", + headers={"Tus-Version": TUS_VERSION}) + +def _parse_metadata(raw: Optional[str]) -> str: + # Raw passthrough; validate/consume in your app if needed. + return raw or "" + +def _new_upload_info(upload_id: str, + kind: str, # "single" | "partial" | "final" + length: Optional[int], + defer_length: bool, + metadata: str, + parts: Optional[List[str]] = None) -> Dict[str, Any]: + return { + "upload_id": upload_id, + "kind": kind, # "single" (default), "partial", or "final" + "length": length, # int or None if deferred/unknown + "defer_length": bool(defer_length), + "metadata": metadata, # raw Upload-Metadata header + "completed": False, + "parts": parts or [], # for final: list of upload_ids (not URLs) + } + +def _load_info_or_404(upload_id: str) -> Dict[str, Any]: + p = info_path(upload_id) + if not _exists(p): + raise HTTPException(404, "Upload not found") + try: + return _read_json(p) + except Exception as e: + raise HTTPException(500, f"Corrupt metadata: {e}") + +def _set_info(upload_id: str, info: Dict[str, Any]) -> None: + _write_json_atomic(info_path(upload_id), info) + +def _ensure_dir(path: str): + os.makedirs(path, exist_ok=False) + +def _atomic_finalize_file(upload_id: str): + """Rename data.part → data and mark completed.""" + upath = upload_dir(upload_id) + p = part_path(upload_id) + f = final_path(upload_id) + if _exists(p): + with open(p, "rb+") as fp: + fp.flush() + os.fsync(fp.fileno()) + os.replace(p, f) + _fsync_dir(upath) + info = _load_info_or_404(upload_id) + info["completed"] = True + _set_info(upload_id, info) + +def _current_offsets(upload_id: str): + f, p = final_path(upload_id), part_path(upload_id) + if _exists(f): + return True, False, _size(f) + if _exists(p): + return False, True, _size(p) + return False, False, 0 + +def _parse_concat_header(h: Optional[str]) -> Optional[Dict[str, Any]]: + if not h: + return None + h = h.strip() + if h == "partial": + return {"type": "partial", "parts": []} + if h.startswith("final;"): + # format: final;/files/a /files/b + rest = h[len("final;"):].strip() + urls = [s for s in rest.split(" ") if s] + return {"type": "final", "parts": urls} + return None + +def _extract_upload_id_from_url(url: str) -> str: + # Accept relative /files/{id} (common) — robust split: + segs = [s for s in url.split("/") if s] + return segs[-1] if segs else url + +def _sum_lengths_or_none(ids: List[str]) -> Optional[int]: + total = 0 + for pid in ids: + info = _load_info_or_404(pid) + if info.get("length") is None: + return None + total += int(info["length"]) + return total + +async def _stream_with_checksum_and_append(file_obj, request: Request, algo: Optional[str]) -> int: + """Stream request body to file, verifying checksum if header present. + Returns bytes written. On checksum mismatch, truncate to original size and raise HTTPException(460).""" + start_pos = file_obj.tell() + # Choose hash + hasher = None + provided_digest = None + if algo: + if algo not in CHECKSUM_ALGOS: + raise HTTPException(400, "Unsupported checksum algorithm") + if algo == "sha1": + hasher = hashlib.sha1() + # elif algo == "md5": hasher = hashlib.md5() + # elif algo == "crc32": ... (custom) + # Read expected checksum + if hasher: + uh = request.headers.get("Upload-Checksum") + if not uh: + # spec: checksum header optional; if algo passed to this fn we must have parsed it already + pass + else: + try: + name, b64 = uh.split(" ", 1) + if name != algo: + raise ValueError() + provided_digest = base64.b64decode(b64.encode("ascii")) + except Exception: + raise HTTPException(400, "Invalid Upload-Checksum") + written = 0 + async for chunk in request.stream(): + if not chunk: + continue + file_obj.write(chunk) + if hasher: + hasher.update(chunk) + written += len(chunk) + # Verify checksum if present + if hasher and provided_digest is not None: + digest = hasher.digest() + if digest != provided_digest: + # rollback appended bytes + file_obj.truncate(start_pos) + file_obj.flush() + os.fsync(file_obj.fileno()) + raise HTTPException(status_code=460, detail="Checksum Mismatch") + file_obj.flush() + os.fsync(file_obj.fileno()) + return written + +def _try_finalize_final(upload_id: str): + """If this is a final upload and all partials are completed, build final data and finalize atomically.""" + info = _load_info_or_404(upload_id) + if info.get("kind") != "final" or info.get("completed"): + return + part_ids = info.get("parts", []) + # Check all partials completed and have data + for pid in part_ids: + pinf = _load_info_or_404(pid) + if not pinf.get("completed"): + return # still not ready + if not _exists(final_path(pid)): + # tolerate leftover .part (e.g., if completed used .part->data). If data missing, can't finalize. + return + # Build final .part by concatenating parts' data in order, then atomically rename + up = upload_dir(upload_id) + os.makedirs(up, exist_ok=True) + ppath = part_path(upload_id) + # Reset/overwrite .part + with open(ppath, "wb") as out: + for pid in part_ids: + with open(final_path(pid), "rb") as src: + for chunk in iter(lambda: src.read(1024 * 1024), b""): + out.write(chunk) + out.flush() + os.fsync(out.fileno()) + # If server can compute length now, set it + length = _sum_lengths_or_none(part_ids) + info["length"] = length if length is not None else info.get("length") + _set_info(upload_id, info) + _atomic_finalize_file(upload_id) + +# ----------------------------- +# Routes +# ----------------------------- +@app.options("/files") +async def tus_options(): + headers = { + "Tus-Version": TUS_VERSION, + "Tus-Extension": TUS_EXTENSIONS, + "Tus-Max-Size": str(MAX_SIZE), + "Tus-Checksum-Algorithm": ",".join(CHECKSUM_ALGOS), + } + return Response(status_code=204, headers=headers) + +@app.post("/files") +async def tus_create(request: Request): + _ensure_tus_version(request) + + metadata = _parse_metadata(request.headers.get("Upload-Metadata")) + concat = _parse_concat_header(request.headers.get("Upload-Concat")) + + # Validate creation modes + hdr_len = request.headers.get("Upload-Length") + hdr_defer = request.headers.get("Upload-Defer-Length") + + if concat and concat["type"] == "partial": + # Partial MUST have Upload-Length (spec) + if hdr_len is None: + raise HTTPException(400, "Partial uploads require Upload-Length") + if hdr_defer is not None: + raise HTTPException(400, "Partial uploads cannot defer length") + elif concat and concat["type"] == "final": + # Final MUST NOT include Upload-Length + if hdr_len is not None or hdr_defer is not None: + raise HTTPException(400, "Final uploads must not include Upload-Length or Upload-Defer-Length") + else: + # Normal single upload: require length or defer + if hdr_len is None and hdr_defer != "1": + raise HTTPException(400, "Must provide Upload-Length or Upload-Defer-Length: 1") + + # Parse length + length: Optional[int] = None + defer = False + if hdr_len is not None: + try: + length = int(hdr_len) + if length < 0: raise ValueError() + except ValueError: + raise HTTPException(400, "Invalid Upload-Length") + if length > MAX_SIZE: + raise HTTPException(413, "Upload too large") + elif not concat or concat["type"] != "final": + # final has no length at creation + defer = (hdr_defer == "1") + + upload_id = str(uuid.uuid4()) + udir = upload_dir(upload_id) + _ensure_dir(udir) + + if concat and concat["type"] == "final": + # Resolve part ids from URLs + part_ids = [_extract_upload_id_from_url(u) for u in concat["parts"]] + # Compute length if possible + sum_len = _sum_lengths_or_none(part_ids) + info = _new_upload_info(upload_id, "final", sum_len, False, metadata, part_ids) + _set_info(upload_id, info) + + # Prepare empty .part (will be filled when partials complete) + with open(part_path(upload_id), "wb") as f: + f.flush(); os.fsync(f.fileno()) + _fsync_dir(udir) + + # If all partials already complete, finalize immediately + _try_finalize_final(upload_id) + + return Response(status_code=201, + headers={"Location": f"/files/{upload_id}", + "Tus-Resumable": TUS_VERSION}) + + # Create partial or single + kind = "partial" if (concat and concat["type"] == "partial") else "single" + info = _new_upload_info(upload_id, kind, length, defer, metadata) + _set_info(upload_id, info) + + # Create empty .part + with open(part_path(upload_id), "wb") as f: + f.flush(); os.fsync(f.fileno()) + _fsync_dir(udir) + + # Creation-With-Upload (optional body) + upload_offset = 0 + has_body = request.headers.get("Content-Length") or request.headers.get("Transfer-Encoding") + if has_body: + ctype = request.headers.get("Content-Type", "") + if ctype != "application/offset+octet-stream": + raise HTTPException(415, "Content-Type must be application/offset+octet-stream for creation-with-upload") + # Checksum header optional; if present, parse algo token + uh = request.headers.get("Upload-Checksum") + algo = None + if uh: + try: + algo = uh.split(" ", 1)[0] + except Exception: + raise HTTPException(400, "Invalid Upload-Checksum") + + async with _lock_for(upload_id): + with open(part_path(upload_id), "ab+") as f: + f.seek(0, os.SEEK_END) + upload_offset = await _stream_with_checksum_and_append(f, request, algo) + + # If length known and we hit it, finalize + inf = _load_info_or_404(upload_id) + if inf["length"] is not None and upload_offset == int(inf["length"]): + _atomic_finalize_file(upload_id) + # If this is a partial that belongs to some final, a watcher could finalize final; here we rely on + # client to create final explicitly (spec). Finalization of final is handled by _try_finalize_final + # when final resource is created (or rechecked on subsequent HEAD/PATCH). + headers = {"Location": f"/files/{upload_id}", "Tus-Resumable": TUS_VERSION} + if upload_offset: + headers["Upload-Offset"] = str(upload_offset) + return Response(status_code=201, headers=headers) + +@app.head("/files/{upload_id}") +async def tus_head(upload_id: str, request: Request): + _ensure_tus_version(request) + info = _load_info_or_404(upload_id) + is_final = info.get("kind") == "final" + + headers = { + "Tus-Resumable": TUS_VERSION, + "Cache-Control": "no-store", + } + if info.get("metadata"): + headers["Upload-Metadata"] = info["metadata"] + + if info.get("length") is not None: + headers["Upload-Length"] = str(int(info["length"])) + elif info.get("defer_length"): + headers["Upload-Defer-Length"] = "1" + + exists_final, exists_part, offset = False, False, 0 + if is_final and not info.get("completed"): + # BEFORE concatenation completes: SHOULD NOT include Upload-Offset + # Try to see if we can finalize now (e.g., partials completed after crash) + _try_finalize_final(upload_id) + info = _load_info_or_404(upload_id) + if info.get("completed"): + # fallthrough to completed case + pass + else: + # For in-progress final, no Upload-Offset; include Upload-Length if computable (already handled above) + return Response(status_code=200, headers=headers) + + # For partials or completed finals + f = final_path(upload_id) + p = part_path(upload_id) + if _exists(f): + exists_final, offset = True, _size(f) + elif _exists(p): + exists_part, offset = True, _size(p) + else: + # if info exists but no data, consider gone + raise HTTPException(410, "Upload gone") + + headers["Upload-Offset"] = str(offset) + return Response(status_code=200, headers=headers) + +@app.patch("/files/{upload_id}") +async def tus_patch(upload_id: str, request: Request): + _ensure_tus_version(request) + info = _load_info_or_404(upload_id) + + if info.get("kind") == "final": + raise HTTPException(403, "Final uploads cannot be patched") + + ctype = request.headers.get("Content-Type", "") + if ctype != "application/offset+octet-stream": + raise HTTPException(415, "Content-Type must be application/offset+octet-stream") + + # Client offset must match server + try: + client_offset = int(request.headers.get("Upload-Offset", "-1")) + if client_offset < 0: raise ValueError() + except ValueError: + raise HTTPException(400, "Invalid or missing Upload-Offset") + + # If length deferred, client may now set Upload-Length (once) + if info.get("length") is None and info.get("defer_length"): + if "Upload-Length" in request.headers: + try: + new_len = int(request.headers["Upload-Length"]) + if new_len < 0: + raise ValueError() + except ValueError: + raise HTTPException(400, "Invalid Upload-Length") + if new_len > MAX_SIZE: + raise HTTPException(413, "Upload too large") + info["length"] = new_len + info["defer_length"] = False + _set_info(upload_id, info) + + # Determine current server offset + f = final_path(upload_id) + p = part_path(upload_id) + if _exists(f): + raise HTTPException(403, "Upload already finalized") + if not _exists(p): + raise HTTPException(404, "Upload not found") + + server_offset = _size(p) + if client_offset != server_offset: + return Response(status_code=409) + + # Optional checksum + uh = request.headers.get("Upload-Checksum") + algo = None + if uh: + try: + algo = uh.split(" ", 1)[0] + except Exception: + raise HTTPException(400, "Invalid Upload-Checksum") + + # Append data (with rollback on checksum mismatch) + async with _lock_for(upload_id): + with open(p, "ab+") as fobj: + fobj.seek(0, os.SEEK_END) + written = await _stream_with_checksum_and_append(fobj, request, algo) + + new_offset = server_offset + written + + # If length known and reached exactly, finalize + info = _load_info_or_404(upload_id) # reload + if info.get("length") is not None and new_offset == int(info["length"]): + _atomic_finalize_file(upload_id) + + # If this is a partial, a corresponding final may exist and be now completable + # We don't maintain reverse index; finalization is triggered when HEAD on final is called. + # (Optional: scan for finals to proactively finalize.) + + return Response(status_code=204, headers={"Tus-Resumable": TUS_VERSION, "Upload-Offset": str(new_offset)}) + +@app.delete("/files/{upload_id}") +async def tus_delete(upload_id: str, request: Request): + _ensure_tus_version(request) + async with _lock_for(upload_id): + udir = upload_dir(upload_id) + for p in (part_path(upload_id), final_path(upload_id), info_path(upload_id)): + try: + os.remove(p) + except FileNotFoundError: + pass + try: + os.rmdir(udir) + except OSError: + pass + return Response(status_code=204, headers={"Tus-Resumable": TUS_VERSION}) +``` + +--- + +## Quick Client Examples (manual) + +```bash +# OPTIONS +curl -i -X OPTIONS http://localhost:8080/files + +# 1) Single upload (known length) +curl -i -X POST http://localhost:8080/files \ + -H "Tus-Resumable: 1.0.0" \ + -H "Upload-Length: 11" \ + -H "Upload-Metadata: filename Zm9vLnR4dA==" +# → Location: /files/ + +# Upload with checksum (sha1 of "hello ") +printf "hello " | curl -i -X PATCH http://localhost:8080/files/ \ + -H "Tus-Resumable: 1.0.0" \ + -H "Content-Type: application/offset+octet-stream" \ + -H "Upload-Offset: 0" \ + -H "Upload-Checksum: sha1 L6v8xR3Lw4N2n9kQox3wL7G0m/I=" \ + --data-binary @- +# (Replace digest with correct base64 for your chunk) + +# 2) Concatenation +# Create partial A (5 bytes) +curl -i -X POST http://localhost:8080/files \ + -H "Tus-Resumable: 1.0.0" \ + -H "Upload-Length: 5" \ + -H "Upload-Concat: partial" +# → Location: /files/ +printf "hello" | curl -i -X PATCH http://localhost:8080/files/ \ + -H "Tus-Resumable: 1.0.0" \ + -H "Content-Type: application/offset+octet-stream" \ + -H "Upload-Offset: 0" \ + --data-binary @- + +# Create partial B (6 bytes) +curl -i -X POST http://localhost:8080/files \ + -H "Tus-Resumable: 1.0.0" \ + -H "Upload-Length: 6" \ + -H "Upload-Concat: partial" +# → Location: /files/ +printf " world" | curl -i -X PATCH http://localhost:8080/files/ \ + -H "Tus-Resumable: 1.0.0" \ + -H "Content-Type: application/offset+octet-stream" \ + -H "Upload-Offset: 0" \ + --data-binary @- + +# Create final (may be before or after partials complete) +curl -i -X POST http://localhost:8080/files \ + -H "Tus-Resumable: 1.0.0" \ + -H "Upload-Concat: final; /files/ /files/" +# HEAD on final will eventually show Upload-Offset once finalized +curl -i -X HEAD http://localhost:8080/files/ -H "Tus-Resumable: 1.0.0" +``` + +--- + +## Implementation Notes (agent hints) + +* **Durability:** every data write `fsync(file)`; after `os.replace` of `*.part → data` or `info.json.tmp → info.json`, also `fsync(parent)`. +* **Checksum:** verify against **this request’s** body only; on mismatch, **truncate back** to previous size and return `460`. +* **Concatenation:** final upload is never `PATCH`ed. Server builds `final.data.part` by concatenating each partial’s **final file** in order, then atomically renames and marks completed. It’s triggered lazily in `HEAD` of final (and right after creation). +* **Crash Recovery:** offset = `size(data.part)` or `size(data)`; `info.json` is canonical for `kind`, `length`, `defer_length`, `completed`, `parts`. +* **Multi-process deployments:** replace `asyncio.Lock` with file locks (`fcntl.flock`) per `upload_id` to synchronize across workers. + + diff --git a/aiprompts/ai_instruct/uppy/uppy.md b/aiprompts/ai_instruct/uppy/uppy.md new file mode 100644 index 00000000..7f3462ef --- /dev/null +++ b/aiprompts/ai_instruct/uppy/uppy.md @@ -0,0 +1,229 @@ +```bash +unpm install @uppy/react +``` + +## Components + +Pre-composed, plug-and-play components: + + renders @uppy/dashboard + renders @uppy/dashboard as a modal + renders @uppy/drag-drop + renders @uppy/progress-bar + renders @uppy/status-bar + +more info see https://uppy.io/docs/react + + +we use tus server for the upload support + +npm install @uppy/tus + +e.g. + +import Uppy from '@uppy/core'; +import Dashboard from '@uppy/dashboard'; +import Tus from '@uppy/tus'; + +import '@uppy/core/dist/style.min.css'; +import '@uppy/dashboard/dist/style.min.css'; + +new Uppy() + .use(Dashboard, { inline: true, target: 'body' }) + + + +======================== +CODE SNIPPETS +======================== + +TITLE: React Dashboard Modal Example with TUS +DESCRIPTION: Demonstrates how to use the DashboardModal component from @uppy/react with the Tus plugin for resumable uploads. +LANGUAGE: jsx +CODE: +``` +/** @jsx React */ +import React from 'react' +import Uppy from '@uppy/core' +import { DashboardModal } from '@uppy/react' +import Tus from '@uppy/tus' + +const uppy = new Uppy({ debug: true, autoProceed: false }) + .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }) + +class Example extends React.Component { + state = { open: false } + + render() { + const { open } = this.state + return ( + + ) + } + // ..snip.. +} +``` + +---------------------------------------- + +TITLE: Installation using npm for @uppy/react +DESCRIPTION: Provides the command to install the @uppy/react package using npm. +LANGUAGE: bash +CODE: +``` +$ npm install @uppy/react @uppy/core @uppy/dashboard @uppy/tus +``` + +---------------------------------------- + +TITLE: Uppy Dashboard and Tus Integration Example (HTML & JavaScript) +DESCRIPTION: This snippet demonstrates how to initialize Uppy with the Dashboard and Tus plugins, configure them, and handle upload success events. +LANGUAGE: html +CODE: +``` + + + + + + +
+ +
+
Uploaded files:
+
    +
    + + + + +``` + +---------------------------------------- + +TITLE: Initialize Uppy with Tus Plugin (JavaScript) +DESCRIPTION: Demonstrates how to initialize Uppy and configure the Tus plugin for resumable uploads. +LANGUAGE: js +CODE: +``` +import Uppy from '@uppy/core' +import Tus from '@uppy/tus' + +const uppy = new Uppy() +uppy.use(Tus, { + endpoint: 'https://tusd.tusdemo.net/files/', // use your tus endpoint here + resume: true, + retryDelays: [0, 1000, 3000, 5000], +}) +``` + +---------------------------------------- + +TITLE: Uppy Core Initialization and Plugin Usage (JavaScript) +DESCRIPTION: This example demonstrates how to initialize Uppy with core functionality and integrate the Tus plugin. It also shows how to listen for upload completion events. +LANGUAGE: javascript +CODE: +``` +import Uppy from '@uppy/core' +import Dashboard from '@uppy/dashboard' +import Tus from '@uppy/tus' + +const uppy = new Uppy() + .use(Dashboard, { trigger: '#select-files' }) + .use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' }) + .on('complete', (result) => { + console.log('Upload result:', result) + }) +``` + +---------------------------------------- + +TITLE: Uppy XHRUpload Configuration (JavaScript) +DESCRIPTION: This snippet shows the basic JavaScript configuration for Uppy, initializing it with the XHRUpload plugin to send files to a specified endpoint. +LANGUAGE: javascript +CODE: +``` +import Uppy from '@uppy/core'; +import XHRUpload from '@uppy/xhr-upload'; + +const uppy = new Uppy({ + debug: true, + autoProceed: false, + restrictions: { + maxFileSize: 100000000, + maxNumberOfFiles: 10, + allowedFileTypes: ['image/*', 'video/*'] + } +}); + +uppy.use(XHRUpload, { + endpoint: 'YOUR_UPLOAD_ENDPOINT_URL', + fieldName: 'files[]', + method: 'post' +}); + +uppy.on('complete', (result) => { + console.log('Upload complete:', result); +}); + +uppy.on('error', (error) => { + console.error('Upload error:', error); +}); +``` + +---------------------------------------- + +TITLE: Install Uppy Core Packages for TUS +DESCRIPTION: Installs the core Uppy package along with the Dashboard and Tus plugins using npm. +LANGUAGE: bash +CODE: +``` +npm install @uppy/core @uppy/dashboard @uppy/tus @uppy/xhr-upload +``` + +======================== +QUESTIONS AND ANSWERS +======================== + +TOPIC: Uppy React Components +Q: What is the purpose of the @uppy/react package? +A: The @uppy/react package provides React component wrappers for Uppy's officially maintained UI plugins. It allows developers to easily integrate Uppy's file uploading capabilities into their React applications. + +---------------------------------------- + +TOPIC: Uppy React Components +Q: How can @uppy/react be installed in a project? +A: The @uppy/react package can be installed using npm with the command '$ npm install @uppy/react'. + +---------------------------------------- + +TOPIC: Uppy React Components +Q: Where can I find more detailed documentation for the @uppy/react plugin? +A: More detailed documentation for the @uppy/react plugin is available on the Uppy website at https://uppy.io/docs/react. \ No newline at end of file diff --git a/examples/clients/gitea.vsh b/examples/clients/gitea.vsh index 2ec17c75..3b9f6b89 100755 --- a/examples/clients/gitea.vsh +++ b/examples/clients/gitea.vsh @@ -3,17 +3,27 @@ import freeflowuniverse.herolib.core.playcmds import freeflowuniverse.herolib.clients.giteaclient -// Configure PostgreSQL client -// heroscript := " -// !!giteaclient.configure -// url: 'git.ourworld.tf' -// user: 'despiegk' -// secret: '' -// " +heroscript := " +!!giteaclient.configure + name: 'default' + url: 'git.ourworld.tf' + user: 'despiegk' + secret: '1' -// // Process the heroscript configuration +!!giteaclient.configure + name: 'two' + url: 'git.ourworld.tf' + user: 'despiegk2' + secret: '2' + +" +// Process the heroscript configuration // playcmds.play(heroscript: heroscript, emptycheck: false)! +println(giteaclient.list(fromdb: true)!) + +//$dbg; + // Get the configured client mut client := giteaclient.get()! @@ -37,4 +47,3 @@ println('Found ${issues.len} issues.') for issue in issues { println(' #${issue.number}: ${issue.title}') } - diff --git a/examples/clients/zinit_rpc_example.vsh b/examples/clients/zinit_rpc.vsh similarity index 91% rename from examples/clients/zinit_rpc_example.vsh rename to examples/clients/zinit_rpc.vsh index 0fb50923..d02ab17d 100755 --- a/examples/clients/zinit_rpc_example.vsh +++ b/examples/clients/zinit_rpc.vsh @@ -1,6 +1,7 @@ #!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run -import freeflowuniverse.herolib.clients.zinit_rpc +import freeflowuniverse.herolib.clients.zinit +import freeflowuniverse.herolib.installers.infra.zinit_installer import os import time @@ -9,34 +10,33 @@ import time println('=== Zinit RPC Client Example ===\n') -// Start Zinit in the background -println('Starting Zinit in background...') -mut zinit_process := os.new_process('/usr/local/bin/zinit') -zinit_process.set_args(['init']) -zinit_process.set_redirect_stdio() -zinit_process.run() +// // Start Zinit in the background +// println('Starting Zinit in background...') +// mut zinit_process := os.new_process('/usr/local/bin/zinit') +// zinit_process.set_args(['init']) +// zinit_process.set_redirect_stdio() +// zinit_process.run() // Wait a moment for Zinit to start up -time.sleep(2000 * time.millisecond) -println('✓ Zinit started') +// time.sleep(2000 * time.millisecond) +// println('✓ Zinit started') // Ensure we clean up Zinit when done -defer { - println('\nCleaning up...') - zinit_process.signal_kill() - zinit_process.wait() - println('✓ Zinit stopped') -} +// defer { +// println('\nCleaning up...') +// zinit_process.signal_kill() +// zinit_process.wait() +// println('✓ Zinit stopped') +// } + +// mut installer := zinit_installer.get()! +// installer.install()! +// installer.start()! // Create a new client -mut client := zinit_rpc.new_client( - name: 'example_client' - socket_path: '/tmp/zinit.sock' -) or { - println('Failed to create client: ${err}') - println('Make sure Zinit is running and the socket exists at /tmp/zinit.sock') - exit(1) -} +mut client := zinit.new()! + +println(client) println('✓ Created Zinit RPC client') @@ -66,7 +66,7 @@ for service_name, state in services { // 3. Create a test service configuration println('\n3. Creating a test service...') test_service_name := 'test_echo_service' -config := zinit_rpc.ServiceConfig{ +config := zinit.ServiceConfig{ exec: '/bin/echo "Hello from test service"' oneshot: true log: 'stdout' @@ -147,7 +147,7 @@ println('\n8. Getting service statistics...') stats := client.service_stats(test_service_name) or { println('Failed to get service stats (service might not be running): ${err}') // Continue anyway - zinit_rpc.ServiceStats{} + zinit.ServiceStats{} } if stats.name != '' { println('✓ Service statistics:') @@ -208,7 +208,7 @@ if subscription_id != 0 { // Get fresh status to make sure service is still running fresh_status := client.service_status(test_service_name) or { println('\n12. Skipping signal test (cannot get service status)') - zinit_rpc.ServiceStatus{} + zinit.ServiceStatus{} } if fresh_status.state == 'Running' && fresh_status.pid > 0 { println('\n12. Sending SIGTERM signal to service...') @@ -258,7 +258,6 @@ server_result := client.system_start_http_server('127.0.0.1:9999') or { } if server_result != '' { println('✓ HTTP server started: ${server_result}') - // Stop the HTTP server client.system_stop_http_server() or { println('Failed to stop HTTP server: ${err}') } println('✓ HTTP server stopped') diff --git a/examples/core/generate.vsh b/examples/core/generate.vsh index 77d4a124..28a7e37f 100755 --- a/examples/core/generate.vsh +++ b/examples/core/generate.vsh @@ -3,14 +3,20 @@ import freeflowuniverse.herolib.core.generator.generic as generator import freeflowuniverse.herolib.core.pathlib -mut args := generator.GeneratorArgs{ - path: '~/code/github/freeflowuniverse/herolib/lib/clients/postgresql_client' - force: true -} - // mut args := generator.GeneratorArgs{ -// path: '~/code/github/freeflowuniverse/herolib/lib' +// path: '~/code/github/freeflowuniverse/herolib/lib/clients' // force: true // } +// mut args2 := generator.GeneratorArgs{ +// path: '~/code/github/freeflowuniverse/herolib/lib/installers/lang/rust' +// force: true +// } +// generator.scan(args2)! + +mut args := generator.GeneratorArgs{ + path: '~/code/github/freeflowuniverse/herolib/lib/installers' + force: true +} + generator.scan(args)! diff --git a/examples/installers/infra/zinit_installer.vsh b/examples/installers/infra/zinit_installer.vsh index ac3bce5f..2817969b 100755 --- a/examples/installers/infra/zinit_installer.vsh +++ b/examples/installers/infra/zinit_installer.vsh @@ -1,4 +1,4 @@ -#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run import freeflowuniverse.herolib.installers.infra.zinit_installer diff --git a/examples/installers/net/mycelium.vsh b/examples/installers/net/mycelium.vsh index bfa7e367..e2101c32 100755 --- a/examples/installers/net/mycelium.vsh +++ b/examples/installers/net/mycelium.vsh @@ -1,14 +1,20 @@ -#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run import freeflowuniverse.herolib.installers.net.mycelium_installer import freeflowuniverse.herolib.clients.mycelium -mut installer := mycelium_installer.get()! +mut installer := mycelium_installer.get(create: true)! +println(installer) + installer.start()! +$dbg; + mut r := mycelium.inspect()! println(r) +// $dbg; + mut client := mycelium.get()! // Send a message to a node by public key diff --git a/examples/lang/python/codewalker.vsh b/examples/lang/python/codewalker.vsh new file mode 100755 index 00000000..3449e853 --- /dev/null +++ b/examples/lang/python/codewalker.vsh @@ -0,0 +1,59 @@ +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run +import freeflowuniverse.herolib.lib.lang.codewalker +import freeflowuniverse.herolib.core.pathlib +import freeflowuniverse.herolib.osal.core as osal + +// Create test directory structure in /tmp/filemap +test_source := '/tmp/filemap' +test_destination := '/tmp/filemap2' + +// Clean up any existing test directories +osal.rm(todelete: test_source)! +osal.rm(todelete: test_destination)! + +// Create source directory +mut source_dir := pathlib.get(test_source)! +source_dir.dir_ensure()! + +// Create test files with content +mut file1 := source_dir.join('file1.txt')! +file1.write('Content of file 1')! + +mut subdir := source_dir.join('subdir')! +subdir.dir_ensure()! + +mut file2 := subdir.join('file2.txt')! +file2.write('Content of file 2')! + +mut file3 := subdir.join('file3.md')! +file3.write('# Markdown file content')! + +println('Test files created in ${test_source}') + +// Create CodeWalker instance +mut cw := codewalker.new(name: 'test', source: test_source)! + +// Verify files are in the map +println('\nFiles in filemap:') +cw.filemap.write() + +// Export files to destination +cw.filemap.export(test_destination)! + +println('\nFiles exported to ${test_destination}') + +// Verify export by listing files in destination +mut dest_dir := pathlib.get(test_destination)! +if dest_dir.exists() { + mut files := dest_dir.list(recursive: true)! + println('\nFiles in destination directory:') + for file in files { + if file.is_file() { + println(' ${file.path}') + println(' Content: ${file.read()!}') + } + } + println('\nExport test completed successfully!') +} else { + println('\nError: Destination directory was not created') +} diff --git a/examples/lang/python/pythonexample.vsh b/examples/lang/python/pythonexample.vsh index 13015b52..3ee6a089 100755 --- a/examples/lang/python/pythonexample.vsh +++ b/examples/lang/python/pythonexample.vsh @@ -1,4 +1,4 @@ -#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run +#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run import freeflowuniverse.herolib.lang.python import json diff --git a/lib/ai/mcp/rhai/logic/prompts/example_script.md b/lib/ai/mcp/rhai/logic/prompts/example_script.md index 777fe75a..e67422ba 100644 --- a/lib/ai/mcp/rhai/logic/prompts/example_script.md +++ b/lib/ai/mcp/rhai/logic/prompts/example_script.md @@ -22,7 +22,7 @@ if repos.len() > 0 { if repo_array.len() > 0 { let repo = repo_array[0]; - print("\nRepository path: " + get_repo_path(repo)); + print("\nRepository path: " + path(repo)); // Check if the repository has changes let has_changes = has_changes(repo); diff --git a/lib/biz/bizmodel/play.v b/lib/biz/bizmodel/play.v index 949fb370..63bb4d2f 100644 --- a/lib/biz/bizmodel/play.v +++ b/lib/biz/bizmodel/play.v @@ -12,6 +12,9 @@ const action_priorities = { } pub fn play(mut plbook PlayBook) ! { + if plbook.exists(filter: 'bizmodel.') == false { + return + } // group actions by which bizmodel they belong to actions_by_biz := arrays.group_by[string, &Action](plbook.find(filter: 'bizmodel.*')!, fn (a &Action) string { diff --git a/lib/clients/giteaclient/giteaclient_factory_.v b/lib/clients/giteaclient/giteaclient_factory_.v index d0edd9aa..e5dd0e79 100644 --- a/lib/clients/giteaclient/giteaclient_factory_.v +++ b/lib/clients/giteaclient/giteaclient_factory_.v @@ -3,6 +3,7 @@ module giteaclient import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( giteaclient_global map[string]&GiteaClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&GiteaClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&GiteaClient { mut obj := GiteaClient{ name: args.name } - if args.name !in giteaclient_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&GiteaClient { + mut context := base.context()! + giteaclient_default = args.name + if args.fromdb || args.name !in giteaclient_global { + mut r := context.redis()! + if r.hexists('context:giteaclient', args.name)! { + data := r.hget('context:giteaclient', args.name)! + if data.len == 0 { + return error('GiteaClient with name: giteaclient does not exist, prob bug.') + } + mut obj := json.decode(GiteaClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('giteaclient', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("GiteaClient with name 'giteaclient' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return giteaclient_global[args.name] or { - println(giteaclient_global) - // bug if we get here because should be in globals - panic('could not get config for giteaclient with name, is bug:${args.name}') + return error('could not get config for giteaclient with name:giteaclient') } } // register the config for the future pub fn set(o GiteaClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + giteaclient_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('giteaclient', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:giteaclient', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('giteaclient', args.name) + mut r := context.redis()! + return r.hexists('context:giteaclient', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('giteaclient', args.name)! - if args.name in giteaclient_global { - // del giteaclient_global[args.name] + mut r := context.redis()! + r.hdel('context:giteaclient', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&GiteaClient { + mut res := []&GiteaClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + giteaclient_global = map[string]&GiteaClient{} + giteaclient_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:giteaclient')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in giteaclient_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o GiteaClient) ! { +fn set_in_mem(o GiteaClient) !GiteaClient { mut o2 := obj_init(o)! - giteaclient_global[o.name] = &o2 - giteaclient_default = o.name + giteaclient_global[o2.name] = &o2 + giteaclient_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'giteaclient.') { + return + } mut install_actions := plbook.find(filter: 'giteaclient.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { giteaclient_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/giteaclient/giteaclient_model.v b/lib/clients/giteaclient/giteaclient_model.v index 0525ffd8..45f940a2 100644 --- a/lib/clients/giteaclient/giteaclient_model.v +++ b/lib/clients/giteaclient/giteaclient_model.v @@ -20,7 +20,7 @@ pub mut: fn (mut self GiteaClient) httpclient() !&httpconnection.HTTPConnection { mut http_conn := httpconnection.new( name: 'giteaclient_${self.name}' - url: self.url + url: self.url )! // Add authentication header if API key is provided @@ -49,7 +49,7 @@ fn obj_init(mycfg_ GiteaClient) !GiteaClient { if mycfg.url.ends_with('/api') { mycfg.url = mycfg.url.replace('/api', '') } - mycfg.url = "https://${mycfg.url}/api/v1" + mycfg.url = 'https://${mycfg.url}/api/v1' if mycfg.secret.len == 0 { return error('secret needs to be filled in for ${mycfg.name}') @@ -59,10 +59,6 @@ fn obj_init(mycfg_ GiteaClient) !GiteaClient { /////////////NORMALLY NO NEED TO TOUCH -pub fn heroscript_dumps(obj GiteaClient) !string { - return encoderhero.encode[GiteaClient](obj)! -} - pub fn heroscript_loads(heroscript string) !GiteaClient { mut obj := encoderhero.decode[GiteaClient](heroscript)! return obj diff --git a/lib/clients/giteaclient/methods.v b/lib/clients/giteaclient/methods.v index c1cb3966..0c27187b 100644 --- a/lib/clients/giteaclient/methods.v +++ b/lib/clients/giteaclient/methods.v @@ -7,14 +7,12 @@ import net.http // List a user's own repositories pub fn (mut client GiteaClient) user_list_repos() ![]Repository { - $dbg; req := httpconnection.Request{ method: .get prefix: '/user/repos' } mut http_client := client.httpclient()! - r:=http_client.get_json_list_generic[Repository](req)! - $dbg; + r := http_client.get_json_list_generic[Repository](req)! return r } diff --git a/lib/clients/giteaclient/models.v b/lib/clients/giteaclient/models.v index b71402e9..a0759eea 100644 --- a/lib/clients/giteaclient/models.v +++ b/lib/clients/giteaclient/models.v @@ -5,41 +5,41 @@ import time pub struct APIError { pub: message string - url string + url string } pub struct AccessToken { pub: - id i64 - name string - scopes []string - sha1 string + id i64 + name string + scopes []string + sha1 string token_last_eight string } pub struct ActionVariable { pub: owner_id i64 - repo_id i64 - name string - data string + repo_id i64 + name string + data string } pub struct Activity { pub: - act_user User + act_user User act_user_id i64 - comment Comment - comment_id i64 - content string - created time.Time - id i64 - is_private bool - op_type string - ref_name string - repo Repository - repo_id i64 - user_id i64 + comment Comment + comment_id i64 + content string + created time.Time + id i64 + is_private bool + op_type string + ref_name string + repo Repository + repo_id i64 + user_id i64 } pub struct AddCollaboratorOption { @@ -49,460 +49,460 @@ pub: pub struct AddTimeOption { pub: - time i64 - created time.Time + time i64 + created time.Time user_name string } pub struct AnnotatedTagObject { pub: sha string - typ string @[json: 'type'] // `type` is a keyword in V + typ string @[json: 'type'] // `type` is a keyword in V url string } pub struct AnnotatedTag { pub: - message string - object AnnotatedTagObject - sha string - tag string - tagger CommitUser - url string + message string + object AnnotatedTagObject + sha string + tag string + tagger CommitUser + url string verification PayloadCommitVerification } pub struct Attachment { pub: browser_download_url string - created_at time.Time - download_count i64 - id i64 - name string - size i64 - uuid string + created_at time.Time + download_count i64 + id i64 + name string + size i64 + uuid string } pub struct Badge { pub: - id i64 - slug string + id i64 + slug string description string - image_url string + image_url string } pub struct Branch { pub: - commit PayloadCommit + commit PayloadCommit effective_branch_protection_name string - enable_status_check bool - name string - protected bool - required_approvals i64 - status_check_contexts []string - user_can_merge bool - user_can_push bool + enable_status_check bool + name string + protected bool + required_approvals i64 + status_check_contexts []string + user_can_merge bool + user_can_push bool } pub struct BranchProtection { pub: branch_name string - rule_name string - enable_push bool - enable_push_whitelist bool - push_whitelist_usernames []string - push_whitelist_teams []string - push_whitelist_deploy_keys bool - enable_merge_whitelist bool - merge_whitelist_usernames []string - merge_whitelist_teams []string - enable_status_check bool - status_check_contexts []string - required_approvals i64 - enable_approvals_whitelist bool - approvals_whitelist_username []string - approvals_whitelist_teams []string - block_on_rejected_reviews bool + rule_name string + enable_push bool + enable_push_whitelist bool + push_whitelist_usernames []string + push_whitelist_teams []string + push_whitelist_deploy_keys bool + enable_merge_whitelist bool + merge_whitelist_usernames []string + merge_whitelist_teams []string + enable_status_check bool + status_check_contexts []string + required_approvals i64 + enable_approvals_whitelist bool + approvals_whitelist_username []string + approvals_whitelist_teams []string + block_on_rejected_reviews bool block_on_official_review_requests bool - block_on_outdated_branch bool - dismiss_stale_approvals bool - ignore_stale_approvals bool - require_signed_commits bool - protected_file_patterns string - unprotected_file_patterns string - created_at time.Time - updated_at time.Time + block_on_outdated_branch bool + dismiss_stale_approvals bool + ignore_stale_approvals bool + require_signed_commits bool + protected_file_patterns string + unprotected_file_patterns string + created_at time.Time + updated_at time.Time } pub struct ChangeFileOperation { pub: operation string // "create", "update", "delete" - path string - content string // base64 encoded + path string + content string // base64 encoded from_path string - sha string + sha string } pub struct ChangeFilesOptions { pub: - author Identity - branch string - committer Identity - dates CommitDateOptions - files []ChangeFileOperation - message string + author Identity + branch string + committer Identity + dates CommitDateOptions + files []ChangeFileOperation + message string new_branch string - signoff bool + signoff bool } pub struct ChangedFile { pub: - additions i64 - changes i64 - contents_url string - deletions i64 - filename string - html_url string + additions i64 + changes i64 + contents_url string + deletions i64 + filename string + html_url string previous_filename string - raw_url string - status string + raw_url string + status string } pub struct Commit { pub: - author User - commit RepoCommit + author User + commit RepoCommit committer User - created time.Time - files []CommitAffectedFiles - html_url string - parents []CommitMeta - sha string - stats CommitStats - url string + created time.Time + files []CommitAffectedFiles + html_url string + parents []CommitMeta + sha string + stats CommitStats + url string } pub struct CommitAffectedFiles { pub: filename string - status string + status string } pub struct CommitDateOptions { pub: - author time.Time + author time.Time committer time.Time } pub struct CommitMeta { pub: created time.Time - sha string - url string + sha string + url string } pub struct CommitStats { pub: additions i64 deletions i64 - total i64 + total i64 } pub struct CommitUser { pub: - date string + date string email string - name string + name string } pub struct Comment { pub: - assets []Attachment - body string - created_at time.Time - html_url string - id i64 - issue_url string - original_author string + assets []Attachment + body string + created_at time.Time + html_url string + id i64 + issue_url string + original_author string original_author_id i64 - pull_request_url string - updated_at time.Time - user User + pull_request_url string + updated_at time.Time + user User } pub struct CreateIssueOption { pub: - title string - assignee string - assignees []string - body string - closed bool - due_date time.Time - labels []i64 + title string + assignee string + assignees []string + body string + closed bool + due_date time.Time + labels []i64 milestone i64 - ref string + ref string } pub struct CreateRepoOption { pub: - name string - auto_init bool - default_branch string - description string - gitignores string - issue_labels string - license string + name string + auto_init bool + default_branch string + description string + gitignores string + issue_labels string + license string object_format_name string // "sha1" or "sha256" - private bool - readme string - template bool - trust_model string // "default", "collaborator", "committer", "collaboratorcommitter" + private bool + readme string + template bool + trust_model string // "default", "collaborator", "committer", "collaboratorcommitter" } pub struct Identity { pub: email string - name string + name string } pub struct InternalTracker { pub: allow_only_contributors_to_track_time bool - enable_issue_dependencies bool - enable_time_tracker bool + enable_issue_dependencies bool + enable_time_tracker bool } pub struct Issue { pub: - id i64 - url string - html_url string - number i64 - user User - original_author string + id i64 + url string + html_url string + number i64 + user User + original_author string original_author_id i64 - title string - body string - ref string - labels []Label - milestone Milestone - assignee User - assignees []User - state string // StateType - is_locked bool - comments i64 - created_at time.Time - updated_at time.Time - closed_at time.Time - due_date time.Time - pull_request PullRequestMeta - repository RepositoryMeta - assets []Attachment - pin_order i64 + title string + body string + ref string + labels []Label + milestone Milestone + assignee User + assignees []User + state string // StateType + is_locked bool + comments i64 + created_at time.Time + updated_at time.Time + closed_at time.Time + due_date time.Time + pull_request PullRequestMeta + repository RepositoryMeta + assets []Attachment + pin_order i64 } pub struct Label { pub: - id i64 - name string - exclusive bool + id i64 + name string + exclusive bool is_archived bool - color string + color string description string - url string + url string } pub struct Milestone { pub: - id i64 - title string - description string - state string // StateType - open_issues i64 + id i64 + title string + description string + state string // StateType + open_issues i64 closed_issues i64 - created_at time.Time - updated_at time.Time - closed_at time.Time - due_on time.Time + created_at time.Time + updated_at time.Time + closed_at time.Time + due_on time.Time } pub struct Organization { pub: - avatar_url string - description string - email string - full_name string - id i64 - location string - name string + avatar_url string + description string + email string + full_name string + id i64 + location string + name string repo_admin_change_team_access bool - username string - visibility string - website string + username string + visibility string + website string } pub struct PayloadCommitVerification { pub: - payload string - reason string + payload string + reason string signature string - signer PayloadUser - verified bool + signer PayloadUser + verified bool } - pub struct PayloadCommit { pub: - added []string - author PayloadUser - committer PayloadUser - id string - message string - modified []string - removed []string - timestamp time.Time - url string + added []string + author PayloadUser + committer PayloadUser + id string + message string + modified []string + removed []string + timestamp time.Time + url string verification PayloadCommitVerification } pub struct PayloadUser { pub: - email string - name string + email string + name string username string } pub struct Permission { pub: admin bool - pull bool - push bool + pull bool + push bool } pub struct PullRequestMeta { pub: - merged bool + merged bool merged_at time.Time - draft bool - html_url string + draft bool + html_url string } pub struct RepoCommit { pub: - author CommitUser - committer CommitUser - message string - tree CommitMeta - url string + author CommitUser + committer CommitUser + message string + tree CommitMeta + url string verification PayloadCommitVerification } pub struct Repository { pub: - id i64 - owner User - name string - full_name string - description string - empty bool - private bool - fork bool - template bool - parent_id i64 - mirror bool - size i64 - language string - languages_url string - html_url string - url string - link string - ssh_url string - clone_url string - website string - stars_count i64 - forks_count i64 - watchers_count i64 - open_issues_count i64 - open_pr_counter i64 - release_counter i64 - default_branch string - archived bool - created_at time.Time - updated_at time.Time - archived_at time.Time - permissions Permission - has_issues bool - internal_tracker InternalTracker - has_wiki bool - has_pull_requests bool - has_projects bool - has_releases bool - has_packages bool - has_actions bool - ignore_whitespace_conflicts bool - allow_merge_commits bool - allow_rebase bool - allow_rebase_explicit bool - allow_squash_merge bool - allow_fast_forward_only_merge bool - allow_rebase_update bool + id i64 + owner User + name string + full_name string + description string + empty bool + private bool + fork bool + template bool + parent_id i64 + mirror bool + size i64 + language string + languages_url string + html_url string + url string + link string + ssh_url string + clone_url string + website string + stars_count i64 + forks_count i64 + watchers_count i64 + open_issues_count i64 + open_pr_counter i64 + release_counter i64 + default_branch string + archived bool + created_at time.Time + updated_at time.Time + archived_at time.Time + permissions Permission + has_issues bool + internal_tracker InternalTracker + has_wiki bool + has_pull_requests bool + has_projects bool + has_releases bool + has_packages bool + has_actions bool + ignore_whitespace_conflicts bool + allow_merge_commits bool + allow_rebase bool + allow_rebase_explicit bool + allow_squash_merge bool + allow_fast_forward_only_merge bool + allow_rebase_update bool default_delete_branch_after_merge bool - default_merge_style string - default_allow_maintainer_edit bool - avatar_url string - internal bool - mirror_interval string - mirror_updated time.Time - repo_transfer RepoTransfer + default_merge_style string + default_allow_maintainer_edit bool + avatar_url string + internal bool + mirror_interval string + mirror_updated time.Time + repo_transfer RepoTransfer } + pub struct RepositoryMeta { pub: - id i64 - name string - owner string + id i64 + name string + owner string full_name string } pub struct Team { pub: - can_create_org_repo bool - description string - id i64 + can_create_org_repo bool + description string + id i64 includes_all_repositories bool - name string - organization Organization - permission string - units []string - units_map map[string]string + name string + organization Organization + permission string + units []string + units_map map[string]string } pub struct RepoTransfer { pub: - doer User + doer User recipient User - teams []Team + teams []Team } pub struct User { pub: - id i64 - login string - full_name string - email string - avatar_url string - language string - is_admin bool - last_login time.Time - created time.Time - restricted bool - active bool - prohibit_login bool - location string - website string - description string - visibility string - followers_count i64 - following_count i64 + id i64 + login string + full_name string + email string + avatar_url string + language string + is_admin bool + last_login time.Time + created time.Time + restricted bool + active bool + prohibit_login bool + location string + website string + description string + visibility string + followers_count i64 + following_count i64 starred_repos_count i64 - username string -} \ No newline at end of file + username string +} diff --git a/lib/clients/giteaclient/readme.md b/lib/clients/giteaclient/readme.md index 4622d9d7..9c8654db 100644 --- a/lib/clients/giteaclient/readme.md +++ b/lib/clients/giteaclient/readme.md @@ -14,8 +14,6 @@ You can configure the client using a HeroScript file: secret: 'your-gitea-api-token' ``` -Save this content in your project's configuration (e.g., `~/.config/hero/config.hcl`) or pass it to a playbook. - ## Usage Example Here's how to get the client and use its methods. diff --git a/lib/clients/ipapi/ipapi_factory_.v b/lib/clients/ipapi/ipapi_factory_.v index 29946e74..bf4838c8 100644 --- a/lib/clients/ipapi/ipapi_factory_.v +++ b/lib/clients/ipapi/ipapi_factory_.v @@ -3,6 +3,7 @@ module ipapi import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( ipapi_global map[string]&IPApi @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&IPApi { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&IPApi { mut obj := IPApi{ name: args.name } - if args.name !in ipapi_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&IPApi { + mut context := base.context()! + ipapi_default = args.name + if args.fromdb || args.name !in ipapi_global { + mut r := context.redis()! + if r.hexists('context:ipapi', args.name)! { + data := r.hget('context:ipapi', args.name)! + if data.len == 0 { + return error('IPApi with name: ipapi does not exist, prob bug.') + } + mut obj := json.decode(IPApi, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('ipapi', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("IPApi with name 'ipapi' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return ipapi_global[args.name] or { - println(ipapi_global) - // bug if we get here because should be in globals - panic('could not get config for ipapi with name, is bug:${args.name}') + return error('could not get config for ipapi with name:ipapi') } } // register the config for the future pub fn set(o IPApi) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + ipapi_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('ipapi', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:ipapi', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('ipapi', args.name) + mut r := context.redis()! + return r.hexists('context:ipapi', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('ipapi', args.name)! - if args.name in ipapi_global { - // del ipapi_global[args.name] + mut r := context.redis()! + r.hdel('context:ipapi', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&IPApi { + mut res := []&IPApi{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + ipapi_global = map[string]&IPApi{} + ipapi_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:ipapi')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in ipapi_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o IPApi) ! { +fn set_in_mem(o IPApi) !IPApi { mut o2 := obj_init(o)! - ipapi_global[o.name] = &o2 - ipapi_default = o.name + ipapi_global[o2.name] = &o2 + ipapi_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'ipapi.') { + return + } mut install_actions := plbook.find(filter: 'ipapi.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { ipapi_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/jina/jina_factory_.v b/lib/clients/jina/jina_factory_.v index 61d9d82a..d55dd343 100644 --- a/lib/clients/jina/jina_factory_.v +++ b/lib/clients/jina/jina_factory_.v @@ -3,6 +3,7 @@ module jina import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( jina_global map[string]&Jina @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&Jina { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&Jina { mut obj := Jina{ name: args.name } - if args.name !in jina_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&Jina { + mut context := base.context()! + jina_default = args.name + if args.fromdb || args.name !in jina_global { + mut r := context.redis()! + if r.hexists('context:jina', args.name)! { + data := r.hget('context:jina', args.name)! + if data.len == 0 { + return error('Jina with name: jina does not exist, prob bug.') + } + mut obj := json.decode(Jina, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('jina', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("Jina with name 'jina' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return jina_global[args.name] or { - println(jina_global) - // bug if we get here because should be in globals - panic('could not get config for jina with name, is bug:${args.name}') + return error('could not get config for jina with name:jina') } } // register the config for the future pub fn set(o Jina) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + jina_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('jina', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:jina', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('jina', args.name) + mut r := context.redis()! + return r.hexists('context:jina', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('jina', args.name)! - if args.name in jina_global { - // del jina_global[args.name] + mut r := context.redis()! + r.hdel('context:jina', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&Jina { + mut res := []&Jina{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + jina_global = map[string]&Jina{} + jina_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:jina')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in jina_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o Jina) ! { +fn set_in_mem(o Jina) !Jina { mut o2 := obj_init(o)! - jina_global[o.name] = &o2 - jina_default = o.name + jina_global[o2.name] = &o2 + jina_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'jina.') { + return + } mut install_actions := plbook.find(filter: 'jina.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { jina_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/livekit/livekit_factory_.v b/lib/clients/livekit/livekit_factory_.v index 5a2eec80..036d61ce 100644 --- a/lib/clients/livekit/livekit_factory_.v +++ b/lib/clients/livekit/livekit_factory_.v @@ -3,6 +3,7 @@ module livekit import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( livekit_global map[string]&LivekitClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&LivekitClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&LivekitClient { mut obj := LivekitClient{ name: args.name } - if args.name !in livekit_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&LivekitClient { + mut context := base.context()! + livekit_default = args.name + if args.fromdb || args.name !in livekit_global { + mut r := context.redis()! + if r.hexists('context:livekit', args.name)! { + data := r.hget('context:livekit', args.name)! + if data.len == 0 { + return error('LivekitClient with name: livekit does not exist, prob bug.') + } + mut obj := json.decode(LivekitClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('livekit', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("LivekitClient with name 'livekit' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return livekit_global[args.name] or { - println(livekit_global) - // bug if we get here because should be in globals - panic('could not get config for livekit with name, is bug:${args.name}') + return error('could not get config for livekit with name:livekit') } } // register the config for the future pub fn set(o LivekitClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + livekit_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('livekit', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:livekit', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('livekit', args.name) + mut r := context.redis()! + return r.hexists('context:livekit', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('livekit', args.name)! - if args.name in livekit_global { - // del livekit_global[args.name] + mut r := context.redis()! + r.hdel('context:livekit', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&LivekitClient { + mut res := []&LivekitClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + livekit_global = map[string]&LivekitClient{} + livekit_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:livekit')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in livekit_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o LivekitClient) ! { +fn set_in_mem(o LivekitClient) !LivekitClient { mut o2 := obj_init(o)! - livekit_global[o.name] = &o2 - livekit_default = o.name + livekit_global[o2.name] = &o2 + livekit_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'livekit.') { + return + } mut install_actions := plbook.find(filter: 'livekit.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { livekit_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/mailclient/mailclient_factory_.v b/lib/clients/mailclient/mailclient_factory_.v index 82a90703..3211ba14 100644 --- a/lib/clients/mailclient/mailclient_factory_.v +++ b/lib/clients/mailclient/mailclient_factory_.v @@ -3,6 +3,7 @@ module mailclient import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( mailclient_global map[string]&MailClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&MailClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&MailClient { mut obj := MailClient{ name: args.name } - if args.name !in mailclient_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&MailClient { + mut context := base.context()! + mailclient_default = args.name + if args.fromdb || args.name !in mailclient_global { + mut r := context.redis()! + if r.hexists('context:mailclient', args.name)! { + data := r.hget('context:mailclient', args.name)! + if data.len == 0 { + return error('MailClient with name: mailclient does not exist, prob bug.') + } + mut obj := json.decode(MailClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('mailclient', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("MailClient with name 'mailclient' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return mailclient_global[args.name] or { - println(mailclient_global) - // bug if we get here because should be in globals - panic('could not get config for mailclient with name, is bug:${args.name}') + return error('could not get config for mailclient with name:mailclient') } } // register the config for the future pub fn set(o MailClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + mailclient_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('mailclient', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:mailclient', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('mailclient', args.name) + mut r := context.redis()! + return r.hexists('context:mailclient', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('mailclient', args.name)! - if args.name in mailclient_global { - // del mailclient_global[args.name] + mut r := context.redis()! + r.hdel('context:mailclient', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&MailClient { + mut res := []&MailClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + mailclient_global = map[string]&MailClient{} + mailclient_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:mailclient')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in mailclient_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o MailClient) ! { +fn set_in_mem(o MailClient) !MailClient { mut o2 := obj_init(o)! - mailclient_global[o.name] = &o2 - mailclient_default = o.name + mailclient_global[o2.name] = &o2 + mailclient_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'mailclient.') { + return + } mut install_actions := plbook.find(filter: 'mailclient.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { mailclient_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/meilisearch/meilisearch_factory_.v b/lib/clients/meilisearch/meilisearch_factory_.v index d1453c38..0ab80331 100644 --- a/lib/clients/meilisearch/meilisearch_factory_.v +++ b/lib/clients/meilisearch/meilisearch_factory_.v @@ -3,6 +3,7 @@ module meilisearch import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( meilisearch_global map[string]&MeilisearchClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&MeilisearchClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&MeilisearchClient { mut obj := MeilisearchClient{ name: args.name } - if args.name !in meilisearch_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&MeilisearchClient { + mut context := base.context()! + meilisearch_default = args.name + if args.fromdb || args.name !in meilisearch_global { + mut r := context.redis()! + if r.hexists('context:meilisearch', args.name)! { + data := r.hget('context:meilisearch', args.name)! + if data.len == 0 { + return error('MeilisearchClient with name: meilisearch does not exist, prob bug.') + } + mut obj := json.decode(MeilisearchClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('meilisearch', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("MeilisearchClient with name 'meilisearch' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return meilisearch_global[args.name] or { - println(meilisearch_global) - // bug if we get here because should be in globals - panic('could not get config for meilisearch with name, is bug:${args.name}') + return error('could not get config for meilisearch with name:meilisearch') } } // register the config for the future pub fn set(o MeilisearchClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + meilisearch_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('meilisearch', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:meilisearch', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('meilisearch', args.name) + mut r := context.redis()! + return r.hexists('context:meilisearch', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('meilisearch', args.name)! - if args.name in meilisearch_global { - // del meilisearch_global[args.name] + mut r := context.redis()! + r.hdel('context:meilisearch', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&MeilisearchClient { + mut res := []&MeilisearchClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + meilisearch_global = map[string]&MeilisearchClient{} + meilisearch_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:meilisearch')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in meilisearch_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o MeilisearchClient) ! { +fn set_in_mem(o MeilisearchClient) !MeilisearchClient { mut o2 := obj_init(o)! - meilisearch_global[o.name] = &o2 - meilisearch_default = o.name + meilisearch_global[o2.name] = &o2 + meilisearch_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'meilisearch.') { + return + } mut install_actions := plbook.find(filter: 'meilisearch.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { meilisearch_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/mycelium/mycelium_factory_.v b/lib/clients/mycelium/mycelium_factory_.v index d0d3c1b5..03bb9dce 100644 --- a/lib/clients/mycelium/mycelium_factory_.v +++ b/lib/clients/mycelium/mycelium_factory_.v @@ -3,6 +3,7 @@ module mycelium import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( mycelium_global map[string]&Mycelium @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&Mycelium { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&Mycelium { mut obj := Mycelium{ name: args.name } - if args.name !in mycelium_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&Mycelium { + mut context := base.context()! + mycelium_default = args.name + if args.fromdb || args.name !in mycelium_global { + mut r := context.redis()! + if r.hexists('context:mycelium', args.name)! { + data := r.hget('context:mycelium', args.name)! + if data.len == 0 { + return error('Mycelium with name: mycelium does not exist, prob bug.') + } + mut obj := json.decode(Mycelium, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('mycelium', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("Mycelium with name 'mycelium' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return mycelium_global[args.name] or { - println(mycelium_global) - // bug if we get here because should be in globals - panic('could not get config for mycelium with name, is bug:${args.name}') + return error('could not get config for mycelium with name:mycelium') } } // register the config for the future pub fn set(o Mycelium) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + mycelium_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('mycelium', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:mycelium', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('mycelium', args.name) + mut r := context.redis()! + return r.hexists('context:mycelium', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('mycelium', args.name)! - if args.name in mycelium_global { - // del mycelium_global[args.name] + mut r := context.redis()! + r.hdel('context:mycelium', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&Mycelium { + mut res := []&Mycelium{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + mycelium_global = map[string]&Mycelium{} + mycelium_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:mycelium')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in mycelium_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o Mycelium) ! { +fn set_in_mem(o Mycelium) !Mycelium { mut o2 := obj_init(o)! - mycelium_global[o.name] = &o2 - mycelium_default = o.name + mycelium_global[o2.name] = &o2 + mycelium_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'mycelium.') { + return + } mut install_actions := plbook.find(filter: 'mycelium.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -91,12 +132,4 @@ pub fn play(mut plbook PlayBook) ! { // switch instance to be used for mycelium pub fn switch(name string) { - mycelium_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/clients/mycelium_rpc/mycelium_rpc_factory_.v b/lib/clients/mycelium_rpc/mycelium_rpc_factory_.v index f8b3fd97..1caafa58 100644 --- a/lib/clients/mycelium_rpc/mycelium_rpc_factory_.v +++ b/lib/clients/mycelium_rpc/mycelium_rpc_factory_.v @@ -3,6 +3,7 @@ module mycelium_rpc import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( mycelium_rpc_global map[string]&MyceliumRPC @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&MyceliumRPC { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&MyceliumRPC { mut obj := MyceliumRPC{ name: args.name } - if args.name !in mycelium_rpc_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&MyceliumRPC { + mut context := base.context()! + mycelium_rpc_default = args.name + if args.fromdb || args.name !in mycelium_rpc_global { + mut r := context.redis()! + if r.hexists('context:mycelium_rpc', args.name)! { + data := r.hget('context:mycelium_rpc', args.name)! + if data.len == 0 { + return error('MyceliumRPC with name: mycelium_rpc does not exist, prob bug.') + } + mut obj := json.decode(MyceliumRPC, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('mycelium_rpc', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("MyceliumRPC with name 'mycelium_rpc' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return mycelium_rpc_global[args.name] or { - println(mycelium_rpc_global) - // bug if we get here because should be in globals - panic('could not get config for mycelium_rpc with name, is bug:${args.name}') + return error('could not get config for mycelium_rpc with name:mycelium_rpc') } } // register the config for the future pub fn set(o MyceliumRPC) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + mycelium_rpc_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('mycelium_rpc', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:mycelium_rpc', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('mycelium_rpc', args.name) + mut r := context.redis()! + return r.hexists('context:mycelium_rpc', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('mycelium_rpc', args.name)! - if args.name in mycelium_rpc_global { - // del mycelium_rpc_global[args.name] + mut r := context.redis()! + r.hdel('context:mycelium_rpc', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&MyceliumRPC { + mut res := []&MyceliumRPC{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + mycelium_rpc_global = map[string]&MyceliumRPC{} + mycelium_rpc_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:mycelium_rpc')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in mycelium_rpc_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o MyceliumRPC) ! { +fn set_in_mem(o MyceliumRPC) !MyceliumRPC { mut o2 := obj_init(o)! - mycelium_rpc_global[o.name] = &o2 - mycelium_rpc_default = o.name + mycelium_rpc_global[o2.name] = &o2 + mycelium_rpc_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'mycelium_rpc.') { + return + } mut install_actions := plbook.find(filter: 'mycelium_rpc.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -91,12 +132,4 @@ pub fn play(mut plbook PlayBook) ! { // switch instance to be used for mycelium_rpc pub fn switch(name string) { - mycelium_rpc_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/clients/openai/openai_factory_.v b/lib/clients/openai/openai_factory_.v index 8aeb1af6..d5ed5033 100644 --- a/lib/clients/openai/openai_factory_.v +++ b/lib/clients/openai/openai_factory_.v @@ -3,6 +3,7 @@ module openai import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( openai_global map[string]&OpenAI @@ -14,75 +15,114 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&OpenAI { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&OpenAI { mut obj := OpenAI{ name: args.name } - if args.name !in openai_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&OpenAI { + mut context := base.context()! + openai_default = args.name + if args.fromdb || args.name !in openai_global { + mut r := context.redis()! + if r.hexists('context:openai', args.name)! { + data := r.hget('context:openai', args.name)! + if data.len == 0 { + return error('OpenAI with name: openai does not exist, prob bug.') + } + mut obj := json.decode(OpenAI, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('openai', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("OpenAI with name 'openai' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return openai_global[args.name] or { - println(openai_global) - // bug if we get here because should be in globals - panic('could not get config for openai with name, is bug:${args.name}') + return error('could not get config for openai with name:openai') } } // register the config for the future pub fn set(o OpenAI) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + openai_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('openai', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:openai', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('openai', args.name) + mut r := context.redis()! + return r.hexists('context:openai', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('openai', args.name)! - if args.name in openai_global { - // del openai_global[args.name] + mut r := context.redis()! + r.hdel('context:openai', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&OpenAI { + mut res := []&OpenAI{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + openai_global = map[string]&OpenAI{} + openai_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:openai')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in openai_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o OpenAI) ! { +fn set_in_mem(o OpenAI) !OpenAI { mut o2 := obj_init(o)! - openai_global[o.name] = &o2 - openai_default = o.name + openai_global[o2.name] = &o2 + openai_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'openai.') { + return + } mut install_actions := plbook.find(filter: 'openai.configure')! if install_actions.len > 0 { for install_action in install_actions { - // println('install_action: ${install_action}') heroscript := install_action.heroscript() mut obj2 := heroscript_loads(heroscript)! set(obj2)! @@ -94,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { openai_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/postgresql_client/postgresql_client_factory_.v b/lib/clients/postgresql_client/postgresql_client_factory_.v index a4271490..5678c02c 100644 --- a/lib/clients/postgresql_client/postgresql_client_factory_.v +++ b/lib/clients/postgresql_client/postgresql_client_factory_.v @@ -3,6 +3,7 @@ module postgresql_client import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( postgresql_client_global map[string]&PostgresqlClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&PostgresqlClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&PostgresqlClient { mut obj := PostgresqlClient{ name: args.name } - if args.name !in postgresql_client_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&PostgresqlClient { + mut context := base.context()! + postgresql_client_default = args.name + if args.fromdb || args.name !in postgresql_client_global { + mut r := context.redis()! + if r.hexists('context:postgresql_client', args.name)! { + data := r.hget('context:postgresql_client', args.name)! + if data.len == 0 { + return error('PostgresqlClient with name: postgresql_client does not exist, prob bug.') + } + mut obj := json.decode(PostgresqlClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('postgresql_client', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("PostgresqlClient with name 'postgresql_client' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return postgresql_client_global[args.name] or { - println(postgresql_client_global) - // bug if we get here because should be in globals - panic('could not get config for postgresql_client with name, is bug:${args.name}') + return error('could not get config for postgresql_client with name:postgresql_client') } } // register the config for the future pub fn set(o PostgresqlClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + postgresql_client_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('postgresql_client', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:postgresql_client', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('postgresql_client', args.name) + mut r := context.redis()! + return r.hexists('context:postgresql_client', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('postgresql_client', args.name)! - if args.name in postgresql_client_global { - // del postgresql_client_global[args.name] + mut r := context.redis()! + r.hdel('context:postgresql_client', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&PostgresqlClient { + mut res := []&PostgresqlClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + postgresql_client_global = map[string]&PostgresqlClient{} + postgresql_client_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:postgresql_client')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in postgresql_client_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o PostgresqlClient) ! { +fn set_in_mem(o PostgresqlClient) !PostgresqlClient { mut o2 := obj_init(o)! - postgresql_client_global[o.name] = &o2 - postgresql_client_default = o.name + postgresql_client_global[o2.name] = &o2 + postgresql_client_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'postgresql_client.') { + return + } mut install_actions := plbook.find(filter: 'postgresql_client.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { postgresql_client_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/qdrant/qdrant_factory_.v b/lib/clients/qdrant/qdrant_factory_.v index 9e143549..72e7b943 100644 --- a/lib/clients/qdrant/qdrant_factory_.v +++ b/lib/clients/qdrant/qdrant_factory_.v @@ -3,6 +3,7 @@ module qdrant import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( qdrant_global map[string]&QDrantClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&QDrantClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&QDrantClient { mut obj := QDrantClient{ name: args.name } - if args.name !in qdrant_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&QDrantClient { + mut context := base.context()! + qdrant_default = args.name + if args.fromdb || args.name !in qdrant_global { + mut r := context.redis()! + if r.hexists('context:qdrant', args.name)! { + data := r.hget('context:qdrant', args.name)! + if data.len == 0 { + return error('QDrantClient with name: qdrant does not exist, prob bug.') + } + mut obj := json.decode(QDrantClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('qdrant', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("QDrantClient with name 'qdrant' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return qdrant_global[args.name] or { - println(qdrant_global) - // bug if we get here because should be in globals - panic('could not get config for qdrant with name, is bug:${args.name}') + return error('could not get config for qdrant with name:qdrant') } } // register the config for the future pub fn set(o QDrantClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + qdrant_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('qdrant', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:qdrant', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('qdrant', args.name) + mut r := context.redis()! + return r.hexists('context:qdrant', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('qdrant', args.name)! - if args.name in qdrant_global { - // del qdrant_global[args.name] + mut r := context.redis()! + r.hdel('context:qdrant', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&QDrantClient { + mut res := []&QDrantClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + qdrant_global = map[string]&QDrantClient{} + qdrant_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:qdrant')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in qdrant_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o QDrantClient) ! { +fn set_in_mem(o QDrantClient) !QDrantClient { mut o2 := obj_init(o)! - qdrant_global[o.name] = &o2 - qdrant_default = o.name + qdrant_global[o2.name] = &o2 + qdrant_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'qdrant.') { + return + } mut install_actions := plbook.find(filter: 'qdrant.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { qdrant_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/rclone/rclone_factory_.v b/lib/clients/rclone/rclone_factory_.v index 10a661f2..c7e1a11c 100644 --- a/lib/clients/rclone/rclone_factory_.v +++ b/lib/clients/rclone/rclone_factory_.v @@ -3,6 +3,7 @@ module rclone import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( rclone_global map[string]&RCloneClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&RCloneClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&RCloneClient { mut obj := RCloneClient{ name: args.name } - if args.name !in rclone_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&RCloneClient { + mut context := base.context()! + rclone_default = args.name + if args.fromdb || args.name !in rclone_global { + mut r := context.redis()! + if r.hexists('context:rclone', args.name)! { + data := r.hget('context:rclone', args.name)! + if data.len == 0 { + return error('RCloneClient with name: rclone does not exist, prob bug.') + } + mut obj := json.decode(RCloneClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('rclone', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("RCloneClient with name 'rclone' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return rclone_global[args.name] or { - println(rclone_global) - // bug if we get here because should be in globals - panic('could not get config for rclone with name, is bug:${args.name}') + return error('could not get config for rclone with name:rclone') } } // register the config for the future pub fn set(o RCloneClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + rclone_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('rclone', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:rclone', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('rclone', args.name) + mut r := context.redis()! + return r.hexists('context:rclone', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('rclone', args.name)! - if args.name in rclone_global { - // del rclone_global[args.name] + mut r := context.redis()! + r.hdel('context:rclone', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&RCloneClient { + mut res := []&RCloneClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + rclone_global = map[string]&RCloneClient{} + rclone_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:rclone')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in rclone_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o RCloneClient) ! { +fn set_in_mem(o RCloneClient) !RCloneClient { mut o2 := obj_init(o)! - rclone_global[o.name] = &o2 - rclone_default = o.name + rclone_global[o2.name] = &o2 + rclone_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'rclone.') { + return + } mut install_actions := plbook.find(filter: 'rclone.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -91,12 +132,4 @@ pub fn play(mut plbook PlayBook) ! { // switch instance to be used for rclone pub fn switch(name string) { - rclone_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/clients/runpod/runpod_factory_.v b/lib/clients/runpod/runpod_factory_.v index 3ff2a255..7940207d 100644 --- a/lib/clients/runpod/runpod_factory_.v +++ b/lib/clients/runpod/runpod_factory_.v @@ -3,6 +3,7 @@ module runpod import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( runpod_global map[string]&RunPod @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&RunPod { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&RunPod { mut obj := RunPod{ name: args.name } - if args.name !in runpod_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&RunPod { + mut context := base.context()! + runpod_default = args.name + if args.fromdb || args.name !in runpod_global { + mut r := context.redis()! + if r.hexists('context:runpod', args.name)! { + data := r.hget('context:runpod', args.name)! + if data.len == 0 { + return error('RunPod with name: runpod does not exist, prob bug.') + } + mut obj := json.decode(RunPod, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('runpod', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("RunPod with name 'runpod' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return runpod_global[args.name] or { - println(runpod_global) - // bug if we get here because should be in globals - panic('could not get config for runpod with name, is bug:${args.name}') + return error('could not get config for runpod with name:runpod') } } // register the config for the future pub fn set(o RunPod) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + runpod_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('runpod', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:runpod', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('runpod', args.name) + mut r := context.redis()! + return r.hexists('context:runpod', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('runpod', args.name)! - if args.name in runpod_global { - // del runpod_global[args.name] + mut r := context.redis()! + r.hdel('context:runpod', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&RunPod { + mut res := []&RunPod{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + runpod_global = map[string]&RunPod{} + runpod_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:runpod')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in runpod_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o RunPod) ! { +fn set_in_mem(o RunPod) !RunPod { mut o2 := obj_init(o)! - runpod_global[o.name] = &o2 - runpod_default = o.name + runpod_global[o2.name] = &o2 + runpod_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'runpod.') { + return + } mut install_actions := plbook.find(filter: 'runpod.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { runpod_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/sendgrid/sendgrid_factory_.v b/lib/clients/sendgrid/sendgrid_factory_.v index e2804849..aa3a27b4 100644 --- a/lib/clients/sendgrid/sendgrid_factory_.v +++ b/lib/clients/sendgrid/sendgrid_factory_.v @@ -3,6 +3,7 @@ module sendgrid import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( sendgrid_global map[string]&SendGrid @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&SendGrid { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&SendGrid { mut obj := SendGrid{ name: args.name } - if args.name !in sendgrid_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&SendGrid { + mut context := base.context()! + sendgrid_default = args.name + if args.fromdb || args.name !in sendgrid_global { + mut r := context.redis()! + if r.hexists('context:sendgrid', args.name)! { + data := r.hget('context:sendgrid', args.name)! + if data.len == 0 { + return error('SendGrid with name: sendgrid does not exist, prob bug.') + } + mut obj := json.decode(SendGrid, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('sendgrid', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("SendGrid with name 'sendgrid' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return sendgrid_global[args.name] or { - println(sendgrid_global) - // bug if we get here because should be in globals - panic('could not get config for sendgrid with name, is bug:${args.name}') + return error('could not get config for sendgrid with name:sendgrid') } } // register the config for the future pub fn set(o SendGrid) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + sendgrid_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('sendgrid', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:sendgrid', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('sendgrid', args.name) + mut r := context.redis()! + return r.hexists('context:sendgrid', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('sendgrid', args.name)! - if args.name in sendgrid_global { - // del sendgrid_global[args.name] + mut r := context.redis()! + r.hdel('context:sendgrid', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&SendGrid { + mut res := []&SendGrid{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + sendgrid_global = map[string]&SendGrid{} + sendgrid_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:sendgrid')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in sendgrid_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o SendGrid) ! { +fn set_in_mem(o SendGrid) !SendGrid { mut o2 := obj_init(o)! - sendgrid_global[o.name] = &o2 - sendgrid_default = o.name + sendgrid_global[o2.name] = &o2 + sendgrid_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'sendgrid.') { + return + } mut install_actions := plbook.find(filter: 'sendgrid.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -91,12 +132,4 @@ pub fn play(mut plbook PlayBook) ! { // switch instance to be used for sendgrid pub fn switch(name string) { - sendgrid_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/clients/vastai/vastai_factory_.v b/lib/clients/vastai/vastai_factory_.v index 849fc68e..3f9f884e 100644 --- a/lib/clients/vastai/vastai_factory_.v +++ b/lib/clients/vastai/vastai_factory_.v @@ -3,6 +3,7 @@ module vastai import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( vastai_global map[string]&VastAI @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&VastAI { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&VastAI { mut obj := VastAI{ name: args.name } - if args.name !in vastai_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&VastAI { + mut context := base.context()! + vastai_default = args.name + if args.fromdb || args.name !in vastai_global { + mut r := context.redis()! + if r.hexists('context:vastai', args.name)! { + data := r.hget('context:vastai', args.name)! + if data.len == 0 { + return error('VastAI with name: vastai does not exist, prob bug.') + } + mut obj := json.decode(VastAI, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('vastai', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("VastAI with name 'vastai' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return vastai_global[args.name] or { - println(vastai_global) - // bug if we get here because should be in globals - panic('could not get config for vastai with name, is bug:${args.name}') + return error('could not get config for vastai with name:vastai') } } // register the config for the future pub fn set(o VastAI) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + vastai_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('vastai', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:vastai', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('vastai', args.name) + mut r := context.redis()! + return r.hexists('context:vastai', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('vastai', args.name)! - if args.name in vastai_global { - // del vastai_global[args.name] + mut r := context.redis()! + r.hdel('context:vastai', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&VastAI { + mut res := []&VastAI{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + vastai_global = map[string]&VastAI{} + vastai_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:vastai')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in vastai_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o VastAI) ! { +fn set_in_mem(o VastAI) !VastAI { mut o2 := obj_init(o)! - vastai_global[o.name] = &o2 - vastai_default = o.name + vastai_global[o2.name] = &o2 + vastai_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'vastai.') { + return + } mut install_actions := plbook.find(filter: 'vastai.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -91,12 +132,4 @@ pub fn play(mut plbook PlayBook) ! { // switch instance to be used for vastai pub fn switch(name string) { - vastai_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/clients/wireguard/wireguard_factory_.v b/lib/clients/wireguard/wireguard_factory_.v index 21a4e0c4..58e4f4f7 100644 --- a/lib/clients/wireguard/wireguard_factory_.v +++ b/lib/clients/wireguard/wireguard_factory_.v @@ -3,6 +3,7 @@ module wireguard import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( wireguard_global map[string]&WireGuard @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&WireGuard { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&WireGuard { mut obj := WireGuard{ name: args.name } - if args.name !in wireguard_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&WireGuard { + mut context := base.context()! + wireguard_default = args.name + if args.fromdb || args.name !in wireguard_global { + mut r := context.redis()! + if r.hexists('context:wireguard', args.name)! { + data := r.hget('context:wireguard', args.name)! + if data.len == 0 { + return error('WireGuard with name: wireguard does not exist, prob bug.') + } + mut obj := json.decode(WireGuard, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('wireguard', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("WireGuard with name 'wireguard' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return wireguard_global[args.name] or { - println(wireguard_global) - // bug if we get here because should be in globals - panic('could not get config for wireguard with name, is bug:${args.name}') + return error('could not get config for wireguard with name:wireguard') } } // register the config for the future pub fn set(o WireGuard) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + wireguard_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('wireguard', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:wireguard', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('wireguard', args.name) + mut r := context.redis()! + return r.hexists('context:wireguard', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('wireguard', args.name)! - if args.name in wireguard_global { - // del wireguard_global[args.name] + mut r := context.redis()! + r.hdel('context:wireguard', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&WireGuard { + mut res := []&WireGuard{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + wireguard_global = map[string]&WireGuard{} + wireguard_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:wireguard')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in wireguard_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o WireGuard) ! { +fn set_in_mem(o WireGuard) !WireGuard { mut o2 := obj_init(o)! - wireguard_global[o.name] = &o2 - wireguard_default = o.name + wireguard_global[o2.name] = &o2 + wireguard_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'wireguard.') { + return + } mut install_actions := plbook.find(filter: 'wireguard.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -93,10 +134,3 @@ pub fn play(mut plbook PlayBook) ! { pub fn switch(name string) { wireguard_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/zerodb_client/zerodb_client_factory_.v b/lib/clients/zerodb_client/zerodb_client_factory_.v index f565ff10..0f4c6f1b 100644 --- a/lib/clients/zerodb_client/zerodb_client_factory_.v +++ b/lib/clients/zerodb_client/zerodb_client_factory_.v @@ -3,6 +3,7 @@ module zerodb_client import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json __global ( zerodb_client_global map[string]&ZeroDBClient @@ -14,71 +15,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&ZeroDBClient { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&ZeroDBClient { mut obj := ZeroDBClient{ name: args.name } - if args.name !in zerodb_client_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&ZeroDBClient { + mut context := base.context()! + zerodb_client_default = args.name + if args.fromdb || args.name !in zerodb_client_global { + mut r := context.redis()! + if r.hexists('context:zerodb_client', args.name)! { + data := r.hget('context:zerodb_client', args.name)! + if data.len == 0 { + return error('ZeroDBClient with name: zerodb_client does not exist, prob bug.') + } + mut obj := json.decode(ZeroDBClient, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('zerodb_client', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("ZeroDBClient with name 'zerodb_client' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return zerodb_client_global[args.name] or { - println(zerodb_client_global) - // bug if we get here because should be in globals - panic('could not get config for zerodb_client with name, is bug:${args.name}') + return error('could not get config for zerodb_client with name:zerodb_client') } } // register the config for the future pub fn set(o ZeroDBClient) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + zerodb_client_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('zerodb_client', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:zerodb_client', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('zerodb_client', args.name) + mut r := context.redis()! + return r.hexists('context:zerodb_client', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('zerodb_client', args.name)! - if args.name in zerodb_client_global { - // del zerodb_client_global[args.name] + mut r := context.redis()! + r.hdel('context:zerodb_client', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&ZeroDBClient { + mut res := []&ZeroDBClient{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + zerodb_client_global = map[string]&ZeroDBClient{} + zerodb_client_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:zerodb_client')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in zerodb_client_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o ZeroDBClient) ! { +fn set_in_mem(o ZeroDBClient) !ZeroDBClient { mut o2 := obj_init(o)! - zerodb_client_global[o.name] = &o2 - zerodb_client_default = o.name + zerodb_client_global[o2.name] = &o2 + zerodb_client_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'zerodb_client.') { + return + } mut install_actions := plbook.find(filter: 'zerodb_client.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -91,12 +132,4 @@ pub fn play(mut plbook PlayBook) ! { // switch instance to be used for zerodb_client pub fn switch(name string) { - zerodb_client_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/clients/zinit_rpc/.heroscript b/lib/clients/zinit/.heroscript similarity index 51% rename from lib/clients/zinit_rpc/.heroscript rename to lib/clients/zinit/.heroscript index 17324919..20dd64e9 100644 --- a/lib/clients/zinit_rpc/.heroscript +++ b/lib/clients/zinit/.heroscript @@ -1,8 +1,8 @@ !!hero_code.generate_client - name:'zinit_rpc' + name:'zinit' classname:'ZinitRPC' - singleton:1 - default:0 + singleton:0 + default:1 hasconfig:1 reset:0 \ No newline at end of file diff --git a/lib/clients/zinit/README.md b/lib/clients/zinit/README.md index 3e5365d4..e54c2cd2 100644 --- a/lib/clients/zinit/README.md +++ b/lib/clients/zinit/README.md @@ -1,152 +1,219 @@ -# Zinit OpenRPC Client +# Zinit RPC Client -This is a V language client for the Zinit service manager, implementing the OpenRPC specification. +This is a V language client for the Zinit process manager, implementing the JSON-RPC API specification for service management operations. ## Overview -Zinit is a service manager that allows you to manage and monitor services on your system. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface. +Zinit is a process manager that provides service monitoring, dependency management, and system control capabilities. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface for administrative tasks such as: + +- Service lifecycle management (start, stop, monitor, forget) +- Service configuration management (create, delete, get) +- Service status and statistics monitoring +- System operations (shutdown, reboot, HTTP server control) +- Log streaming and monitoring ## Features -- Complete implementation of all methods in the Zinit OpenRPC specification -- Type-safe API with proper error handling -- Comprehensive documentation -- Helper functions for common operations -- Example code for all operations +- **✅ 100% API Coverage**: Complete implementation of all 18 methods in the Zinit JSON-RPC specification +- **✅ Production Tested**: All methods tested and working against real Zinit instances +- **✅ Type-safe API**: Proper V struct definitions with comprehensive error handling +- **✅ Subscription Support**: Proper handling of streaming/subscription methods +- **✅ Unix Socket Transport**: Reliable communication via Unix domain sockets +- **✅ Comprehensive Documentation**: Extensive documentation with working examples ## Usage ### Basic Example ```v -import freeflowuniverse.heroweb.clients.zinit +import freeflowuniverse.herolib.clients.zinit -fn main() { - // Create a new client with the default socket path - mut client := zinit.new_default_client() - - // List all services - services := client.service_list() or { - println('Error: ${err}') - return - } - - // Print the services - for name, state in services { - println('${name}: ${state}') - } - - // Get status of a specific service - if services.len > 0 { - service_name := services.keys()[0] - status := client.service_status(service_name) or { - println('Error: ${err}') - return - } - - println('Service: ${status.name}') - println('State: ${status.state}') - println('PID: ${status.pid}') +// Create a new client +mut client := zinit.get(create:true)! + +// List all services +services := client.service_list()! +for service_name, state in services { + println('Service: ${service_name}, State: ${state}') +} + +// Get detailed status of a specific service +status := client.service_status('redis')! +println('Service: ${status.name}') +println('PID: ${status.pid}') +println('State: ${status.state}') +println('Target: ${status.target}') + +// Start a service +client.service_start('redis')! + +// Stop a service +client.service_stop('redis')! +``` + +### Service Configuration Management + +```v +import freeflowuniverse.herolib.clients.zinit + +mut client := zinit.new_client()! + +// Create a new service configuration +config := zinit.ServiceConfig{ + exec: '/usr/bin/redis-server' + oneshot: false + log: 'stdout' + env: { + 'REDIS_PORT': '6379' + 'REDIS_HOST': '0.0.0.0' } + shutdown_timeout: 30 +} + +// Create the service +path := client.service_create('redis', config)! +println('Service created at: ${path}') + +// Get service configuration +retrieved_config := client.service_get('redis')! +println('Service exec: ${retrieved_config.exec}') + +// Delete service configuration +result := client.service_delete('redis')! +println('Delete result: ${result}') +``` + +### Service Statistics + +```v +import freeflowuniverse.herolib.clients.zinit + +mut client := zinit.new_client()! + +// Get service statistics +stats := client.service_stats('redis')! +println('Service: ${stats.name}') +println('PID: ${stats.pid}') +println('Memory Usage: ${stats.memory_usage} bytes') +println('CPU Usage: ${stats.cpu_usage}%') + +// Print child process statistics +for child in stats.children { + println('Child PID: ${child.pid}, Memory: ${child.memory_usage}, CPU: ${child.cpu_usage}%') } ``` -### Creating and Managing Services +### Log Streaming ```v -import freeflowuniverse.heroweb.clients.zinit +import freeflowuniverse.herolib.clients.zinit -fn main() { - mut client := zinit.new_default_client() - - // Create a new service configuration - config := zinit.ServiceConfig{ - exec: '/bin/echo "Hello, World!"' - oneshot: true - log: zinit.log_stdout - env: { - 'ENV_VAR': 'value' - } - } - - // Create the service - client.service_create('hello', config) or { - println('Error creating service: ${err}') - return - } - - // Start the service - client.service_start('hello') or { - println('Error starting service: ${err}') - return - } - - // Get the service logs - logs := client.stream_current_logs('hello') or { - println('Error getting logs: ${err}') - return - } - - for log in logs { - println(log) - } - - // Clean up - client.service_stop('hello') or {} - client.service_forget('hello') or {} - client.service_delete('hello') or {} +mut client := zinit.new_client()! + +// Get current logs for all services +logs := client.stream_current_logs(name: '')! +for log in logs { + println(log) } + +// Get current logs for a specific service +redis_logs := client.stream_current_logs(name: 'redis')! +for log in redis_logs { + println('Redis: ${log}') +} + +// Subscribe to log stream (returns subscription ID) +subscription_id := client.stream_subscribe_logs(name: 'redis')! +println('Subscribed to logs with ID: ${subscription_id}') ``` ## API Reference -### Client Creation +### Service Management Methods -- `new_client(socket_path string) &Client` - Create a new client with a custom socket path -- `new_default_client() &Client` - Create a new client with the default socket path (`/tmp/zinit.sock`) +- `service_list()` - List all services and their states +- `service_status(name)` - Get detailed status of a service +- `service_start(name)` - Start a service +- `service_stop(name)` - Stop a service +- `service_monitor(name)` - Start monitoring a service +- `service_forget(name)` - Stop monitoring a service +- `service_kill(name, signal)` - Send signal to a service -### Service Management +### Service Configuration Methods -- `service_list() !map[string]string` - List all services and their states -- `service_status(name string) !ServiceStatus` - Get detailed status of a service -- `service_start(name string) !` - Start a service -- `service_stop(name string) !` - Stop a service -- `service_monitor(name string) !` - Start monitoring a service -- `service_forget(name string) !` - Stop monitoring a service -- `service_kill(name string, signal string) !` - Send a signal to a service -- `service_create(name string, config ServiceConfig) !string` - Create a new service -- `service_delete(name string) !string` - Delete a service -- `service_get(name string) !ServiceConfig` - Get a service configuration -- `service_stats(name string) !ServiceStats` - Get memory and CPU usage statistics +- `service_create(name, config)` - Create service configuration +- `service_delete(name)` - Delete service configuration +- `service_get(name)` - Get service configuration -### System Operations +### Monitoring Methods -- `system_shutdown() !` - Stop all services and power off the system -- `system_reboot() !` - Stop all services and reboot the system -- `system_start_http_server(address string) !string` - Start an HTTP/RPC server -- `system_stop_http_server() !` - Stop the HTTP/RPC server +- `service_stats(name)` - Get service statistics -### Logs +### System Methods -- `stream_current_logs(name ?string) ![]string` - Get current logs -- `stream_subscribe_logs(name ?string) !string` - Subscribe to log messages +- `system_shutdown()` - Shutdown the system +- `system_reboot()` - Reboot the system +- `system_start_http_server(address)` - Start HTTP server +- `system_stop_http_server()` - Stop HTTP server -## Constants +### Streaming Methods -- `default_socket_path` - Default Unix socket path (`/tmp/zinit.sock`) -- `state_running`, `state_success`, `state_error`, etc. - Common service states -- `target_up`, `target_down` - Common service targets -- `log_null`, `log_ring`, `log_stdout` - Common log types -- `signal_term`, `signal_kill`, etc. - Common signals +- `stream_current_logs(args)` - Get current logs (returns array of log lines) +- `stream_subscribe_logs(args)` - Subscribe to log stream (returns subscription ID) -## Helper Functions +### Discovery Methods -- `new_service_config(exec string) ServiceConfig` - Create a basic service configuration -- `new_oneshot_service_config(exec string) ServiceConfig` - Create a oneshot service configuration -- `is_service_not_found_error(err IError) bool` - Check if an error is a "service not found" error -- `format_memory_usage(bytes i64) string` - Format memory usage in human-readable format -- `format_cpu_usage(cpu_percent f64) string` - Format CPU usage +- `rpc_discover()` - Get OpenRPC specification + +## Configuration + +### Using the Factory Pattern + +```v +import freeflowuniverse.herolib.clients.zinit + +// Get client using factory (recommended) +mut client := zinit.get()! + +// Use the client +services := client.service_list()! +``` + +### Example Heroscript Configuration + +```hero +!!zinit.configure + name: 'production' + socket_path: '/tmp/zinit.sock' +``` + +## Error Handling + +The client provides comprehensive error handling for all Zinit-specific error codes: + +- `-32000`: Service not found +- `-32001`: Service already monitored +- `-32002`: Service is up +- `-32003`: Service is down +- `-32004`: Invalid signal +- `-32005`: Config error +- `-32006`: Shutting down +- `-32007`: Service already exists +- `-32008`: Service file error + +```v +import freeflowuniverse.herolib.clients.zinit + +mut client := zinit.new_client()! + +// Handle specific errors +client.service_start('nonexistent') or { + if err.msg().contains('Service not found') { + println('Service does not exist') + } else { + println('Other error: ${err}') + } +} +``` -## License -MIT \ No newline at end of file diff --git a/lib/clients/zinit/error.v b/lib/clients/zinit/error.v deleted file mode 100644 index 6bdb8032..00000000 --- a/lib/clients/zinit/error.v +++ /dev/null @@ -1,18 +0,0 @@ -module zinit - -// Request Types for Zinit API -// -// This file contains all the request types used by the Zinit API. - -// ZinitError represents an error returned by the zinit API -pub struct ZinitError { -pub mut: - code int // Error code - message string // Error message - data string // Additional error data -} - -// Error implements the error interface for ZinitError -pub fn (e ZinitError) msg() string { - return 'Zinit Error ${e.code}: ${e.message} - ${e.data}' -} diff --git a/lib/clients/zinit/factory.v b/lib/clients/zinit/factory.v deleted file mode 100644 index 101f28fe..00000000 --- a/lib/clients/zinit/factory.v +++ /dev/null @@ -1,23 +0,0 @@ -module zinit - -import freeflowuniverse.herolib.schemas.jsonrpc - -// Client is an OpenRPC client for Zinit -pub struct Client { -mut: - rpc_client &jsonrpc.Client -} - -@[params] -pub struct ClientParams { - path string = '/tmp/zinit.sock' // Path to the Zinit RPC socket -} - -// new_client creates a new Zinit RPC client with a custom socket path -pub fn new_client(args_ ClientParams) &Client { - mut args := args_ - mut cl := jsonrpc.new_unix_socket_client(args.path) - return &Client{ - rpc_client: cl - } -} diff --git a/lib/clients/zinit/instruct.md b/lib/clients/zinit/instruct.md new file mode 100644 index 00000000..0bfd5017 --- /dev/null +++ b/lib/clients/zinit/instruct.md @@ -0,0 +1,20 @@ +| RPC Call | Example In | Example Out | 1-Sentence Description | +|---------|-----------|------------|------------------------| +| `rpc.discover` | `{}` | `{ "openrpc": "1.2.6", "info": { "version": "1.0.0", "title": "Zinit JSON-RPC API" } }` | Returns the full OpenRPC specification of the Zinit API. | +| `service_list` | `{}` | `{ "service1": "Running", "service2": "Success", "service3": "Error" }` | Lists all managed services and their current states. | +| `service_status` | `{ "name": "redis" }` | `{ "name": "redis", "pid": 1234, "state": "Running", "target": "Up", "after": { "dependency1": "Success", "dependency2": "Running" } }` | Returns detailed status including PID, state, dependencies, and target. | +| `service_start` | `{ "name": "redis" }` | `null` | Starts a specified service; returns no result on success. | +| `service_stop` | `{ "name": "redis" }` | `null` | Stops a specified service; returns no result on success. | +| `service_monitor` | `{ "name": "redis" }` | `null` | Starts monitoring a service using its configuration from the config directory. | +| `service_forget` | `{ "name": "redis" }` | `null` | Stops monitoring a service; only allowed for stopped services. | +| `service_kill` | `{ "name": "redis", "signal": "SIGTERM" }` | `null` | Sends a signal (e.g., SIGTERM) to a running service. | +| `system_shutdown` | `{}` | `null` | Stops all services and powers off the system. | +| `system_reboot` | `{}` | `null` | Stops all services and reboots the system. | +| `service_create` | `{ "name": "redis", "content": { "exec": "redis-server", "oneshot": false, "after": ["network"], "log": "stdout", "env": { "REDIS_PASSWORD": "secret" }, "shutdown_timeout": 30 } }` | `"service_config/redis"` | Creates a new service configuration file with specified settings. | +| `service_delete` | `{ "name": "redis" }` | `"service deleted"` | Deletes a service configuration file. | +| `service_get` | `{ "name": "redis" }` | `{ "exec": "redis-server", "oneshot": false, "after": ["network"] }` | Retrieves the configuration content of a service. | +| `service_stats` | `{ "name": "redis" }` | `{ "name": "redis", "pid": 1234, "memory_usage": 10485760, "cpu_usage": 2.5, "children": [ { "pid": 1235, "memory_usage": 5242880, "cpu_usage": 1.2 } ] }` | Returns memory and CPU usage statistics for a running service. | +| `system_start_http_server` | `{ "address": "127.0.0.1:8080" }` | `"HTTP server started at 127.0.0.1:8080"` | Starts an HTTP/RPC server on the specified network address. | +| `system_stop_http_server` | `{}` | `null` | Stops the currently running HTTP/RPC server. | +| `stream_currentLogs` | `{ "name": "redis" }` | `["2023-01-01T12:00:00 redis: Starting service", "2023-01-01T12:00:02 redis: Service started"]` | Returns current logs; optionally filtered by service name. | +| `stream_subscribeLogs` | `{ "name": "redis" }` | `"2023-01-01T12:00:00 redis: Service started"` | Subscribes to real-time log messages, optionally filtered by service. | diff --git a/lib/clients/zinit/model.v b/lib/clients/zinit/model.v deleted file mode 100644 index 7ba84e82..00000000 --- a/lib/clients/zinit/model.v +++ /dev/null @@ -1,73 +0,0 @@ -module zinit - -// ServiceCreateResponse represents the response from service_create -pub struct ServiceCreateResponse { -pub mut: - path string // Path to the created service file -} - -// ServiceDeleteResponse represents the response from service_delete -pub struct ServiceDeleteResponse { -pub mut: - result string // Result of the delete operation -} - -// SystemStartHttpServerResponse represents the response from system_start_http_server -pub struct SystemStartHttpServerResponse { -pub mut: - result string // Result of starting the HTTP server -} - -// StreamCurrentLogsResponse represents the response from stream_currentLogs -pub struct StreamCurrentLogsResponse { -pub mut: - logs []string // Log entries -} - -// StreamSubscribeLogsResponse represents the response from stream_subscribeLogs -pub struct StreamSubscribeLogsResponse { -pub mut: - subscription_id string // ID of the log subscription -} - -// Module version information -pub const version = '1.0.0' -pub const author = 'Hero Code' -pub const license = 'MIT' - -// Default socket path for zinit -pub const default_socket_path = '/tmp/zinit.sock' - -// Common service states -pub const state_running = 'Running' -pub const state_success = 'Success' -pub const state_error = 'Error' -pub const state_stopped = 'Stopped' -pub const state_failed = 'Failed' - -// Common service targets -pub const target_up = 'Up' -pub const target_down = 'Down' - -// Common log types -pub const log_null = 'null' -pub const log_ring = 'ring' -pub const log_stdout = 'stdout' - -// Common signals -pub const signal_term = 'SIGTERM' -pub const signal_kill = 'SIGKILL' -pub const signal_hup = 'SIGHUP' -pub const signal_usr1 = 'SIGUSR1' -pub const signal_usr2 = 'SIGUSR2' - -// JSON-RPC error codes as defined in the OpenRPC specification -pub const error_service_not_found = -32000 -pub const error_service_already_monitored = -32001 -pub const error_service_is_up = -32002 -pub const error_service_is_down = -32003 -pub const error_invalid_signal = -32004 -pub const error_config_error = -32005 -pub const error_shutting_down = -32006 -pub const error_service_already_exists = -32007 -pub const error_service_file_error = -32008 diff --git a/lib/clients/zinit/model_openrpc.v b/lib/clients/zinit/model_openrpc.v new file mode 100644 index 00000000..8a226779 --- /dev/null +++ b/lib/clients/zinit/model_openrpc.v @@ -0,0 +1,63 @@ +module zinit + +// ServiceStatus represents detailed status information for a service +pub struct ServiceStatus { +pub mut: + name string // Service name + pid u32 // Process ID of the running service (if running) + state string // Current state of the service (Running, Success, Error, etc.) + target string // Target state of the service (Up, Down) + after map[string]string // Dependencies of the service and their states +} + +// ServiceConfig represents the configuration for a zinit service +pub struct ServiceConfig { +pub mut: + exec string // Command to run + test string // Test command (optional) + oneshot bool // Whether the service should be restarted (maps to one_shot in Zinit) + after []string // Services that must be running before this one starts + log string // How to handle service output (null, ring, stdout) + env map[string]string // Environment variables for the service + dir string // Working directory for the service + shutdown_timeout u64 // Maximum time to wait for service to stop during shutdown +} + +// ServiceStats represents memory and CPU usage statistics for a service +pub struct ServiceStats { +pub mut: + name string // Service name + pid u32 // Process ID of the service + memory_usage u64 // Memory usage in bytes + cpu_usage f32 // CPU usage as a percentage (0-100) + children []ChildStats // Stats for child processes +} + +// ChildStats represents statistics for a child process +pub struct ChildStats { +pub mut: + pid u32 // Process ID of the child process + memory_usage u64 // Memory usage in bytes + cpu_usage f32 // CPU usage as a percentage (0-100) +} + +// ServiceCreateParams represents parameters for service_create method +pub struct ServiceCreateParams { +pub mut: + name string // Name of the service to create + content ServiceConfig // Configuration for the service +} + +// ServiceKillParams represents parameters for service_kill method +pub struct ServiceKillParams { +pub mut: + name string // Name of the service to kill + signal string // Signal to send (e.g., SIGTERM, SIGKILL) +} + +// LogParams represents parameters for log streaming methods +@[params] +pub struct LogParams { +pub mut: + name string // Optional service name filter +} diff --git a/lib/clients/zinit/service.v b/lib/clients/zinit/service.v deleted file mode 100644 index e6209619..00000000 --- a/lib/clients/zinit/service.v +++ /dev/null @@ -1,175 +0,0 @@ -module zinit - -import freeflowuniverse.herolib.schemas.jsonrpc - -// ServiceConfig represents the configuration for a zinit service -pub struct ServiceConfig { -pub mut: - exec string // Command to run - oneshot bool // Whether the service should be restarted - after []string // Services that must be running before this one starts - log string // How to handle service output (null, ring, stdout) - env map[string]string // Environment variables for the service - shutdown_timeout int // Maximum time to wait for service to stop during shutdown -} - -// KillParams represents the parameters for the service_kill method -pub struct KillParams { -pub: - name string // Name of the service to kill - signal string // Signal to send (e.g., SIGTERM, SIGKILL) -} - -// RpcDiscoverResponse represents the response from rpc.discover -pub struct RpcDiscoverResponse { -pub mut: - spec map[string]string // OpenRPC specification -} - -// rpc_discover returns the OpenRPC specification for the API -pub fn (mut c Client) rpc_discover() !RpcDiscoverResponse { - request := jsonrpc.new_request_generic('rpc.discover', []string{}) - response := c.rpc_client.send[[]string, map[string]string](request)! - return RpcDiscoverResponse{ - spec: response - } -} - -// // Response Models for Zinit API -// // -// // This file contains all the response models used by the Zinit API. -// // These models are used as type parameters in the response generics. - -// // ServiceListResponse represents the response from service_list -// pub struct ServiceListResponse { -// pub mut: -// // Map of service names to their current states -// services map[string]string -// } - -// service_list lists all services managed by Zinit -// Returns a map of service names to their current states -pub fn (mut c Client) service_list() !map[string]string { - request := jsonrpc.new_request_generic('service_list', map[string]string{}) - services := c.rpc_client.send[map[string]string, map[string]string](request)! - // return ServiceListResponse{ - // services: services - // } - return services -} - -// ServiceStatusResponse represents the response from service_status -pub struct ServiceStatusResponse { -pub mut: - name string // Service name - pid int // Process ID of the running service (if running) - state string // Current state of the service (Running, Success, Error, etc.) - target string // Target state of the service (Up, Down) - after map[string]string // Dependencies of the service and their states -} - -// service_status shows detailed status information for a specific service -// name: the name of the service -pub fn (mut c Client) service_status(name string) !ServiceStatusResponse { - request := jsonrpc.new_request_generic('service_status', name) - - // Use a direct struct mapping instead of manual conversion - return c.rpc_client.send[string, ServiceStatusResponse](request)! -} - -// service_start starts a service -// name: the name of the service to start -pub fn (mut c Client) service_start(name string) ! { - request := jsonrpc.new_request_generic('service_start', name) - c.rpc_client.send[string, string](request)! -} - -// service_stop stops a service -// name: the name of the service to stop -pub fn (mut c Client) service_stop(name string) ! { - request := jsonrpc.new_request_generic('service_stop', name) - c.rpc_client.send[string, string](request)! -} - -// service_monitor starts monitoring a service -// The service configuration is loaded from the config directory -// name: the name of the service to monitor -pub fn (mut c Client) service_monitor(name string) ! { - request := jsonrpc.new_request_generic('service_monitor', name) - c.rpc_client.send[string, string](request)! -} - -// service_delete deletes a service configuration file -// name: the name of the service to delete -pub fn (mut c Client) service_delete(name string) !ServiceDeleteResponse { - request := jsonrpc.new_request_generic('service_delete', name) - result := c.rpc_client.send[string, string](request)! - return ServiceDeleteResponse{ - result: result - } -} - -// service_forget stops monitoring a service -// You can only forget a stopped service -// name: the name of the service to forget -pub fn (mut c Client) service_forget(name string) ! { - request := jsonrpc.new_request_generic('service_forget', name) - c.rpc_client.send[string, string](request)! -} - -// TODO: make sure the signal is a valid signal and enumerator do as @[params] so its optional - -// service_kill sends a signal to a running service -// name: the name of the service to send the signal to -// signal: the signal to send (e.g., SIGTERM, SIGKILL) -pub fn (mut c Client) service_kill(name string, signal string) ! { - params := KillParams{ - name: name - signal: signal - } - - request := jsonrpc.new_request_generic('service_kill', params) - c.rpc_client.send[KillParams, string](request)! -} - -// CreateServiceParams represents the parameters for the service_create method -struct CreateServiceParams { - name string // Name of the service to create - content ServiceConfig // Configuration for the service -} - -// service_create creates a new service configuration file -// name: the name of the service to create -// config: the service configuration -pub fn (mut c Client) service_create(name string, config ServiceConfig) !ServiceCreateResponse { - params := CreateServiceParams{ - name: name - content: config - } - - request := jsonrpc.new_request_generic('service_create', params) - path := c.rpc_client.send[CreateServiceParams, string](request)! - return ServiceCreateResponse{ - path: path - } -} - -// service_get gets a service configuration file -// name: the name of the service to get -pub fn (mut c Client) service_get(name string) !ServiceConfigResponse { - request := jsonrpc.new_request_generic('service_get', { - 'name': name - }) - - // We need to handle the conversion from ServiceConfig to ServiceConfigResponse - config := c.rpc_client.send[map[string]string, ServiceConfig](request)! - - return ServiceConfigResponse{ - exec: config.exec - oneshot: config.oneshot - after: config.after - log: config.log - env: config.env - shutdown_timeout: config.shutdown_timeout - } -} diff --git a/lib/clients/zinit/service_config.v b/lib/clients/zinit/service_config.v deleted file mode 100644 index 16133fea..00000000 --- a/lib/clients/zinit/service_config.v +++ /dev/null @@ -1,33 +0,0 @@ -module zinit - -pub struct ServiceConfigResponse { -pub mut: - exec string // Command to run - oneshot bool // Whether the service should be restarted - after []string // Services that must be running before this one starts - log string // How to handle service output (null, ring, stdout) - env map[string]string // Environment variables for the service - shutdown_timeout int // Maximum time to wait for service to stop during shutdown -} - -// Helper function to create a basic service configuration -pub fn new_service_config(exec string) ServiceConfig { - return ServiceConfig{ - exec: exec - oneshot: false - log: log_stdout - env: map[string]string{} - shutdown_timeout: 30 - } -} - -// Helper function to create a oneshot service configuration -pub fn new_oneshot_service_config(exec string) ServiceConfig { - return ServiceConfig{ - exec: exec - oneshot: true - log: log_stdout - env: map[string]string{} - shutdown_timeout: 30 - } -} diff --git a/lib/clients/zinit/service_stats.v b/lib/clients/zinit/service_stats.v deleted file mode 100644 index 0cd74f28..00000000 --- a/lib/clients/zinit/service_stats.v +++ /dev/null @@ -1,44 +0,0 @@ -module zinit - -import freeflowuniverse.herolib.schemas.jsonrpc - -// ServiceStatsResponse represents the response from service_stats -pub struct ServiceStatsResponse { -pub mut: - name string // Service name - pid int // Process ID of the service - memory_usage i64 // Memory usage in bytes - cpu_usage f64 // CPU usage as a percentage (0-100) - children []ChildStatsResponse // Stats for child processes -} - -// ChildStatsResponse represents statistics for a child process -pub struct ChildStatsResponse { -pub mut: - pid int // Process ID of the child process - memory_usage i64 // Memory usage in bytes - cpu_usage f64 // CPU usage as a percentage (0-100) -} - -// Serv - -// service_stats gets memory and CPU usage statistics for a service -// name: the name of the service to get stats for -pub fn (mut c Client) service_stats(name string) !ServiceStatsResponse { - request := jsonrpc.new_request_generic('service_stats', name) - - // We need to handle the conversion from the raw response to our model - raw_stats := c.rpc_client.send[string, map[string]string](request)! - - // Parse the raw stats into our response model - mut children := []ChildStatsResponse{} - // In a real implementation, we would parse the children from the raw response - - return ServiceStatsResponse{ - name: raw_stats['name'] or { '' } - pid: raw_stats['pid'].int() - memory_usage: raw_stats['memory_usage'].i64() - cpu_usage: raw_stats['cpu_usage'].f64() - children: children - } -} diff --git a/lib/clients/zinit/system.v b/lib/clients/zinit/system.v deleted file mode 100644 index 65a8e70e..00000000 --- a/lib/clients/zinit/system.v +++ /dev/null @@ -1,71 +0,0 @@ -module zinit - -import freeflowuniverse.herolib.schemas.jsonrpc - -// system_shutdown stops all services and powers off the system -pub fn (mut c Client) system_shutdown() ! { - request := jsonrpc.new_request_generic('system_shutdown', []string{}) - c.rpc_client.send[[]string, string](request)! -} - -// system_reboot stops all services and reboots the system -pub fn (mut c Client) system_reboot() ! { - request := jsonrpc.new_request_generic('system_reboot', []string{}) - c.rpc_client.send[[]string, string](request)! -} - -// system_start_http_server starts an HTTP/RPC server at the specified address -// address: the network address to bind the server to (e.g., '127.0.0.1:8080') -pub fn (mut c Client) system_start_http_server(address string) !SystemStartHttpServerResponse { - request := jsonrpc.new_request_generic('system_start_http_server', address) - result := c.rpc_client.send[string, string](request)! - return SystemStartHttpServerResponse{ - result: result - } -} - -// system_stop_http_server stops the HTTP/RPC server if running -pub fn (mut c Client) system_stop_http_server() ! { - request := jsonrpc.new_request_generic('system_stop_http_server', []string{}) - c.rpc_client.send[[]string, string](request)! -} - -@[params] -pub struct LogParams { - name string -} - -// stream_current_logs gets current logs from zinit and monitored services -// name: optional service name filter. If provided, only logs from this service will be returned -pub fn (mut c Client) stream_current_logs(args LogParams) ![]string { - mut logs := []string{} - - if args.name != '' { - request := jsonrpc.new_request_generic('stream_currentLogs', { - 'name': args.name - }) - logs = c.rpc_client.send[map[string]string, map[string]string](request)! - } else { - request := jsonrpc.new_request_generic('stream_currentLogs', map[string]string{}) - logs = c.rpc_client.send[[]map[string]string, map[string]string](request)! - } - return logs -} - -// stream_subscribe_logs subscribes to log messages generated by zinit and monitored services -// name: optional service name filter. If provided, only logs from this service will be returned -pub fn (mut c Client) stream_subscribe_logs(name ?string) !StreamSubscribeLogsResponse { - mut subscription_id := '' - - if service_name := name { - request := jsonrpc.new_request_generic('stream_subscribeLogs', service_name) - subscription_id = c.rpc_client.send[string, string](request)! - } else { - request := jsonrpc.new_request_generic('stream_subscribeLogs', []string{}) - subscription_id = c.rpc_client.send[[]string, string](request)! - } - - return StreamSubscribeLogsResponse{ - subscription_id: subscription_id - } -} diff --git a/lib/clients/zinit/tools.v b/lib/clients/zinit/tools.v deleted file mode 100644 index 66d1ba89..00000000 --- a/lib/clients/zinit/tools.v +++ /dev/null @@ -1,19 +0,0 @@ -module zinit - -// Helper function to format memory usage in human-readable format -pub fn format_memory_usage(bytes i64) string { - if bytes < 1024 { - return '${bytes} B' - } else if bytes < 1024 * 1024 { - return '${bytes / 1024} KB' - } else if bytes < 1024 * 1024 * 1024 { - return '${bytes / 1024 / 1024} MB' - } else { - return '${bytes / 1024 / 1024 / 1024} GB' - } -} - -// Helper function to format CPU usage -pub fn format_cpu_usage(cpu_percent f64) string { - return '${cpu_percent:.1f}%' -} diff --git a/lib/clients/zinit_rpc/zinit_rpc.v b/lib/clients/zinit/zinit.v similarity index 87% rename from lib/clients/zinit_rpc/zinit_rpc.v rename to lib/clients/zinit/zinit.v index 09f734e2..dcd885a3 100644 --- a/lib/clients/zinit_rpc/zinit_rpc.v +++ b/lib/clients/zinit/zinit.v @@ -1,9 +1,10 @@ -module zinit_rpc +module zinit import freeflowuniverse.herolib.schemas.jsonrpc +import freeflowuniverse.herolib.schemas.jsonrpcmodel // Helper function to get or create the RPC client -fn (mut c ZinitRPC) get_client() !&jsonrpc.Client { +fn (mut c ZinitRPC) client_() !&jsonrpc.Client { if client := c.rpc_client { return client } @@ -16,23 +17,23 @@ fn (mut c ZinitRPC) get_client() !&jsonrpc.Client { // Admin methods // rpc_discover returns the OpenRPC specification for the API -pub fn (mut c ZinitRPC) rpc_discover() !OpenRPCSpec { - mut client := c.get_client()! +pub fn (mut c ZinitRPC) rpc_discover() !jsonrpcmodel.OpenRPCSpec { + mut client := c.client_()! request := jsonrpc.new_request_generic('rpc.discover', []string{}) - return client.send[[]string, OpenRPCSpec](request)! + return client.send[[]string, jsonrpcmodel.OpenRPCSpec](request)! } // service_list lists all services managed by Zinit // Returns a map of service names to their current states pub fn (mut c ZinitRPC) service_list() !map[string]string { - mut client := c.get_client()! + mut client := c.client_()! request := jsonrpc.new_request_generic('service_list', []string{}) return client.send[[]string, map[string]string](request)! } // service_status shows detailed status information for a specific service pub fn (mut c ZinitRPC) service_status(name string) !ServiceStatus { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -42,7 +43,7 @@ pub fn (mut c ZinitRPC) service_status(name string) !ServiceStatus { // service_start starts a service pub fn (mut c ZinitRPC) service_start(name string) ! { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -52,7 +53,7 @@ pub fn (mut c ZinitRPC) service_start(name string) ! { // service_stop stops a service pub fn (mut c ZinitRPC) service_stop(name string) ! { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -63,7 +64,7 @@ pub fn (mut c ZinitRPC) service_stop(name string) ! { // service_monitor starts monitoring a service // The service configuration is loaded from the config directory pub fn (mut c ZinitRPC) service_monitor(name string) ! { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -74,7 +75,7 @@ pub fn (mut c ZinitRPC) service_monitor(name string) ! { // service_forget stops monitoring a service // You can only forget a stopped service pub fn (mut c ZinitRPC) service_forget(name string) ! { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -84,7 +85,7 @@ pub fn (mut c ZinitRPC) service_forget(name string) ! { // service_kill sends a signal to a running service pub fn (mut c ZinitRPC) service_kill(name string, signal string) ! { - mut client := c.get_client()! + mut client := c.client_()! params := ServiceKillParams{ name: name signal: signal @@ -95,18 +96,21 @@ pub fn (mut c ZinitRPC) service_kill(name string, signal string) ! { // service_create creates a new service configuration file pub fn (mut c ZinitRPC) service_create(name string, config ServiceConfig) !string { - mut client := c.get_client()! + mut client := c.client_()! params := ServiceCreateParams{ name: name content: config } + println(params) + $dbg; request := jsonrpc.new_request_generic('service_create', params) + $dbg; return client.send[ServiceCreateParams, string](request)! } // service_delete deletes a service configuration file pub fn (mut c ZinitRPC) service_delete(name string) !string { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -116,7 +120,7 @@ pub fn (mut c ZinitRPC) service_delete(name string) !string { // service_get gets a service configuration file pub fn (mut c ZinitRPC) service_get(name string) !ServiceConfig { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -126,7 +130,7 @@ pub fn (mut c ZinitRPC) service_get(name string) !ServiceConfig { // service_stats gets memory and CPU usage statistics for a service pub fn (mut c ZinitRPC) service_stats(name string) !ServiceStats { - mut client := c.get_client()! + mut client := c.client_()! params := { 'name': name } @@ -138,21 +142,21 @@ pub fn (mut c ZinitRPC) service_stats(name string) !ServiceStats { // system_shutdown stops all services and powers off the system pub fn (mut c ZinitRPC) system_shutdown() ! { - mut client := c.get_client()! + mut client := c.client_()! request := jsonrpc.new_request_generic('system_shutdown', []string{}) client.send[[]string, string](request)! } // system_reboot stops all services and reboots the system pub fn (mut c ZinitRPC) system_reboot() ! { - mut client := c.get_client()! + mut client := c.client_()! request := jsonrpc.new_request_generic('system_reboot', []string{}) client.send[[]string, string](request)! } // system_start_http_server starts an HTTP/RPC server at the specified address pub fn (mut c ZinitRPC) system_start_http_server(address string) !string { - mut client := c.get_client()! + mut client := c.client_()! params := { 'address': address } @@ -162,7 +166,7 @@ pub fn (mut c ZinitRPC) system_start_http_server(address string) !string { // system_stop_http_server stops the HTTP/RPC server if running pub fn (mut c ZinitRPC) system_stop_http_server() ! { - mut client := c.get_client()! + mut client := c.client_()! request := jsonrpc.new_request_generic('system_stop_http_server', []string{}) client.send[[]string, string](request)! } @@ -171,7 +175,7 @@ pub fn (mut c ZinitRPC) system_stop_http_server() ! { // stream_current_logs gets current logs from zinit and monitored services pub fn (mut c ZinitRPC) stream_current_logs(args LogParams) ![]string { - mut client := c.get_client()! + mut client := c.client_()! if args.name != '' { params := { 'name': args.name @@ -187,7 +191,7 @@ pub fn (mut c ZinitRPC) stream_current_logs(args LogParams) ![]string { // stream_subscribe_logs subscribes to log messages generated by zinit and monitored services // Returns a subscription ID that can be used to manage the subscription pub fn (mut c ZinitRPC) stream_subscribe_logs(args LogParams) !u64 { - mut client := c.get_client()! + mut client := c.client_()! if args.name != '' { params := { 'name': args.name diff --git a/lib/clients/zinit/zinit_factory_.v b/lib/clients/zinit/zinit_factory_.v new file mode 100644 index 00000000..88895839 --- /dev/null +++ b/lib/clients/zinit/zinit_factory_.v @@ -0,0 +1,136 @@ +module zinit + +import freeflowuniverse.herolib.core.base +import freeflowuniverse.herolib.core.playbook { PlayBook } +import freeflowuniverse.herolib.ui.console +import json + +__global ( + zinit_global map[string]&ZinitRPC + zinit_default string +) + +/////////FACTORY + +@[params] +pub struct ArgsGet { +pub mut: + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist +} + +pub fn new(args ArgsGet) !&ZinitRPC { + mut obj := ZinitRPC{ + name: args.name + } + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&ZinitRPC { + mut context := base.context()! + zinit_default = args.name + if args.fromdb || args.name !in zinit_global { + mut r := context.redis()! + if r.hexists('context:zinit', args.name)! { + data := r.hget('context:zinit', args.name)! + if data.len == 0 { + return error('ZinitRPC with name: zinit does not exist, prob bug.') + } + mut obj := json.decode(ZinitRPC, data)! + set_in_mem(obj)! + } else { + if args.create { + new(args)! + } else { + return error("ZinitRPC with name 'zinit' does not exist") + } + } + return get(name: args.name)! // no longer from db nor create + } + return zinit_global[args.name] or { + return error('could not get config for zinit with name:zinit') + } +} + +// register the config for the future +pub fn set(o ZinitRPC) ! { + mut o2 := set_in_mem(o)! + zinit_default = o2.name + mut context := base.context()! + mut r := context.redis()! + r.hset('context:zinit', o2.name, json.encode(o2))! +} + +// does the config exists? +pub fn exists(args ArgsGet) !bool { + mut context := base.context()! + mut r := context.redis()! + return r.hexists('context:zinit', args.name)! +} + +pub fn delete(args ArgsGet) ! { + mut context := base.context()! + mut r := context.redis()! + r.hdel('context:zinit', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&ZinitRPC { + mut res := []&ZinitRPC{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + zinit_global = map[string]&ZinitRPC{} + zinit_default = '' + } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:zinit')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in zinit_global { + res << client + } + } + return res +} + +// only sets in mem, does not set as config +fn set_in_mem(o ZinitRPC) !ZinitRPC { + mut o2 := obj_init(o)! + zinit_global[o2.name] = &o2 + zinit_default = o2.name + return o2 +} + +pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'zinit.') { + return + } + mut install_actions := plbook.find(filter: 'zinit.configure')! + if install_actions.len > 0 { + for install_action in install_actions { + heroscript := install_action.heroscript() + mut obj2 := heroscript_loads(heroscript)! + set(obj2)! + } + } +} + +// switch instance to be used for zinit +pub fn switch(name string) { + zinit_default = name +} diff --git a/lib/clients/zinit/zinit_model.v b/lib/clients/zinit/zinit_model.v new file mode 100644 index 00000000..72a56134 --- /dev/null +++ b/lib/clients/zinit/zinit_model.v @@ -0,0 +1,48 @@ +module zinit + +import freeflowuniverse.herolib.data.encoderhero +import freeflowuniverse.herolib.schemas.jsonrpc +import os + +pub const version = '0.0.0' +const singleton = true +const default = false + +// // Factory function to create a new ZinitRPC client instance +// @[params] +// pub struct NewClientArgs { +// pub mut: +// name string = 'default' +// socket_path string +// } + +// pub fn new_client(args NewClientArgs) !&ZinitRPC { +// mut client := ZinitRPC{ +// name: args.name +// socket_path: args.socket_path +// } +// client = obj_init(client)! +// return &client +// } + +@[heap] +pub struct ZinitRPC { +pub mut: + name string = 'default' + socket_path string + rpc_client ?&jsonrpc.Client @[skip] +} + +// your checking & initialization code if needed +fn obj_init(mycfg_ ZinitRPC) !ZinitRPC { + mut mycfg := mycfg_ + if mycfg.socket_path == '' { + mycfg.socket_path = '/tmp/zinit.sock' + } + return mycfg +} + +pub fn heroscript_loads(heroscript string) !ZinitRPC { + mut obj := encoderhero.decode[ZinitRPC](heroscript)! + return obj +} diff --git a/lib/clients/zinit_rpc/readme.md b/lib/clients/zinit_rpc/readme.md deleted file mode 100644 index c28fa6bd..00000000 --- a/lib/clients/zinit_rpc/readme.md +++ /dev/null @@ -1,222 +0,0 @@ -# Zinit RPC Client - -This is a V language client for the Zinit process manager, implementing the JSON-RPC API specification for service management operations. - -## Overview - -Zinit is a process manager that provides service monitoring, dependency management, and system control capabilities. This client provides a comprehensive API to interact with Zinit via its JSON-RPC interface for administrative tasks such as: - -- Service lifecycle management (start, stop, monitor, forget) -- Service configuration management (create, delete, get) -- Service status and statistics monitoring -- System operations (shutdown, reboot, HTTP server control) -- Log streaming and monitoring - -## Features - -- **✅ 100% API Coverage**: Complete implementation of all 18 methods in the Zinit JSON-RPC specification -- **✅ Production Tested**: All methods tested and working against real Zinit instances -- **✅ Type-safe API**: Proper V struct definitions with comprehensive error handling -- **✅ Subscription Support**: Proper handling of streaming/subscription methods -- **✅ Unix Socket Transport**: Reliable communication via Unix domain sockets -- **✅ Comprehensive Documentation**: Extensive documentation with working examples - -## Usage - -### Basic Example - -```v -import freeflowuniverse.herolib.clients.zinit_rpc - -// Create a new client -mut client := zinit_rpc.new_client( - name: 'my_client' - socket_path: '/tmp/zinit.sock' -)! - -// List all services -services := client.service_list()! -for service_name, state in services { - println('Service: ${service_name}, State: ${state}') -} - -// Get detailed status of a specific service -status := client.service_status('redis')! -println('Service: ${status.name}') -println('PID: ${status.pid}') -println('State: ${status.state}') -println('Target: ${status.target}') - -// Start a service -client.service_start('redis')! - -// Stop a service -client.service_stop('redis')! -``` - -### Service Configuration Management - -```v -import freeflowuniverse.herolib.clients.zinit_rpc - -mut client := zinit_rpc.new_client()! - -// Create a new service configuration -config := zinit_rpc.ServiceConfig{ - exec: '/usr/bin/redis-server' - oneshot: false - log: 'stdout' - env: { - 'REDIS_PORT': '6379' - 'REDIS_HOST': '0.0.0.0' - } - shutdown_timeout: 30 -} - -// Create the service -path := client.service_create('redis', config)! -println('Service created at: ${path}') - -// Get service configuration -retrieved_config := client.service_get('redis')! -println('Service exec: ${retrieved_config.exec}') - -// Delete service configuration -result := client.service_delete('redis')! -println('Delete result: ${result}') -``` - -### Service Statistics - -```v -import freeflowuniverse.herolib.clients.zinit_rpc - -mut client := zinit_rpc.new_client()! - -// Get service statistics -stats := client.service_stats('redis')! -println('Service: ${stats.name}') -println('PID: ${stats.pid}') -println('Memory Usage: ${stats.memory_usage} bytes') -println('CPU Usage: ${stats.cpu_usage}%') - -// Print child process statistics -for child in stats.children { - println('Child PID: ${child.pid}, Memory: ${child.memory_usage}, CPU: ${child.cpu_usage}%') -} -``` - -### Log Streaming - -```v -import freeflowuniverse.herolib.clients.zinit_rpc - -mut client := zinit_rpc.new_client()! - -// Get current logs for all services -logs := client.stream_current_logs(name: '')! -for log in logs { - println(log) -} - -// Get current logs for a specific service -redis_logs := client.stream_current_logs(name: 'redis')! -for log in redis_logs { - println('Redis: ${log}') -} - -// Subscribe to log stream (returns subscription ID) -subscription_id := client.stream_subscribe_logs(name: 'redis')! -println('Subscribed to logs with ID: ${subscription_id}') -``` - -## API Reference - -### Service Management Methods - -- `service_list()` - List all services and their states -- `service_status(name)` - Get detailed status of a service -- `service_start(name)` - Start a service -- `service_stop(name)` - Stop a service -- `service_monitor(name)` - Start monitoring a service -- `service_forget(name)` - Stop monitoring a service -- `service_kill(name, signal)` - Send signal to a service - -### Service Configuration Methods - -- `service_create(name, config)` - Create service configuration -- `service_delete(name)` - Delete service configuration -- `service_get(name)` - Get service configuration - -### Monitoring Methods - -- `service_stats(name)` - Get service statistics - -### System Methods - -- `system_shutdown()` - Shutdown the system -- `system_reboot()` - Reboot the system -- `system_start_http_server(address)` - Start HTTP server -- `system_stop_http_server()` - Stop HTTP server - -### Streaming Methods - -- `stream_current_logs(args)` - Get current logs (returns array of log lines) -- `stream_subscribe_logs(args)` - Subscribe to log stream (returns subscription ID) - -### Discovery Methods - -- `rpc_discover()` - Get OpenRPC specification - -## Configuration - -### Using the Factory Pattern - -```v -import freeflowuniverse.herolib.clients.zinit_rpc - -// Get client using factory (recommended) -mut client := zinit_rpc.get()! - -// Use the client -services := client.service_list()! -``` - -### Example Heroscript Configuration - -```hero -!!zinit_rpc.configure - name: 'production' - socket_path: '/tmp/zinit.sock' -``` - -## Error Handling - -The client provides comprehensive error handling for all Zinit-specific error codes: - -- `-32000`: Service not found -- `-32001`: Service already monitored -- `-32002`: Service is up -- `-32003`: Service is down -- `-32004`: Invalid signal -- `-32005`: Config error -- `-32006`: Shutting down -- `-32007`: Service already exists -- `-32008`: Service file error - -```v -import freeflowuniverse.herolib.clients.zinit_rpc - -mut client := zinit_rpc.new_client()! - -// Handle specific errors -client.service_start('nonexistent') or { - if err.msg().contains('Service not found') { - println('Service does not exist') - } else { - println('Other error: ${err}') - } -} -``` - - diff --git a/lib/clients/zinit_rpc/zinit_rpc_factory_.v b/lib/clients/zinit_rpc/zinit_rpc_factory_.v deleted file mode 100644 index 743fb50a..00000000 --- a/lib/clients/zinit_rpc/zinit_rpc_factory_.v +++ /dev/null @@ -1,102 +0,0 @@ -module zinit_rpc - -import freeflowuniverse.herolib.core.base -import freeflowuniverse.herolib.core.playbook { PlayBook } -import freeflowuniverse.herolib.ui.console - -__global ( - zinit_rpc_global map[string]&ZinitRPC - zinit_rpc_default string -) - -/////////FACTORY - -@[params] -pub struct ArgsGet { -pub mut: - name string -} - -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&ZinitRPC { - mut context := base.context()! - mut args := args_get(args_) - mut obj := ZinitRPC{ - name: args.name - } - if args.name !in zinit_rpc_global { - if !exists(args)! { - set(obj)! - } else { - heroscript := context.hero_config_get('zinit_rpc', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! - } - } - return zinit_rpc_global[args.name] or { - println(zinit_rpc_global) - // bug if we get here because should be in globals - panic('could not get config for zinit_rpc with name, is bug:${args.name}') - } -} - -// register the config for the future -pub fn set(o ZinitRPC) ! { - set_in_mem(o)! - mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('zinit_rpc', o.name, heroscript)! -} - -// does the config exists? -pub fn exists(args_ ArgsGet) !bool { - mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('zinit_rpc', args.name) -} - -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) - mut context := base.context()! - context.hero_config_delete('zinit_rpc', args.name)! - if args.name in zinit_rpc_global { - // del zinit_rpc_global[args.name] - } -} - -// only sets in mem, does not set as config -fn set_in_mem(o ZinitRPC) ! { - mut o2 := obj_init(o)! - zinit_rpc_global[o.name] = &o2 - zinit_rpc_default = o.name -} - -pub fn play(mut plbook PlayBook) ! { - mut install_actions := plbook.find(filter: 'zinit_rpc.configure')! - if install_actions.len > 0 { - for install_action in install_actions { - heroscript := install_action.heroscript() - mut obj2 := heroscript_loads(heroscript)! - set(obj2)! - } - } -} - -// switch instance to be used for zinit_rpc -pub fn switch(name string) { - zinit_rpc_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/clients/zinit_rpc/zinit_rpc_model.v b/lib/clients/zinit_rpc/zinit_rpc_model.v deleted file mode 100644 index 764a63e9..00000000 --- a/lib/clients/zinit_rpc/zinit_rpc_model.v +++ /dev/null @@ -1,163 +0,0 @@ -module zinit_rpc - -import freeflowuniverse.herolib.data.encoderhero -import freeflowuniverse.herolib.schemas.jsonrpc - -pub const version = '0.0.0' -const singleton = true -const default = false - -// Default configuration for Zinit JSON-RPC API -pub const default_socket_path = '/tmp/zinit.sock' - -// THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED - -@[heap] -pub struct ZinitRPC { -pub mut: - name string = 'default' - socket_path string = default_socket_path // Unix socket path for RPC server - rpc_client ?&jsonrpc.Client @[skip] -} - -// your checking & initialization code if needed -fn obj_init(mycfg_ ZinitRPC) !ZinitRPC { - mut mycfg := mycfg_ - if mycfg.socket_path == '' { - mycfg.socket_path = default_socket_path - } - // For now, we'll initialize the client when needed - // The actual client will be created in the factory - return mycfg -} - -// Response structs based on OpenRPC specification - -// OpenRPCSpec represents the OpenRPC specification structure -pub struct OpenRPCSpec { -pub mut: - openrpc string @[json: 'openrpc'] // OpenRPC version - info OpenRPCInfo @[json: 'info'] // API information - methods []OpenRPCMethod @[json: 'methods'] // Available methods - servers []OpenRPCServer @[json: 'servers'] // Server information -} - -// OpenRPCInfo represents API information -pub struct OpenRPCInfo { -pub mut: - version string @[json: 'version'] // API version - title string @[json: 'title'] // API title - description string @[json: 'description'] // API description - license OpenRPCLicense @[json: 'license'] // License information -} - -// OpenRPCLicense represents license information -pub struct OpenRPCLicense { -pub mut: - name string @[json: 'name'] // License name -} - -// OpenRPCMethod represents an RPC method -pub struct OpenRPCMethod { -pub mut: - name string @[json: 'name'] // Method name - description string @[json: 'description'] // Method description - // Note: params and result are dynamic and would need more complex handling -} - -// OpenRPCServer represents server information -pub struct OpenRPCServer { -pub mut: - name string @[json: 'name'] // Server name - url string @[json: 'url'] // Server URL -} - -// ServiceStatus represents detailed status information for a service -pub struct ServiceStatus { -pub mut: - name string @[json: 'name'] // Service name - pid u32 @[json: 'pid'] // Process ID of the running service (if running) - state string @[json: 'state'] // Current state of the service (Running, Success, Error, etc.) - target string @[json: 'target'] // Target state of the service (Up, Down) - after map[string]string @[json: 'after'] // Dependencies of the service and their states -} - -// ServiceConfig represents the configuration for a zinit service -pub struct ServiceConfig { -pub mut: - exec string @[json: 'exec'] // Command to run - test string @[json: 'test'] // Test command (optional) - oneshot bool @[json: 'oneshot'] // Whether the service should be restarted (maps to one_shot in Zinit) - after []string @[json: 'after'] // Services that must be running before this one starts - log string @[json: 'log'] // How to handle service output (null, ring, stdout) - env map[string]string @[json: 'env'] // Environment variables for the service - dir string @[json: 'dir'] // Working directory for the service - shutdown_timeout u64 @[json: 'shutdown_timeout'] // Maximum time to wait for service to stop during shutdown -} - -// ServiceStats represents memory and CPU usage statistics for a service -pub struct ServiceStats { -pub mut: - name string @[json: 'name'] // Service name - pid u32 @[json: 'pid'] // Process ID of the service - memory_usage u64 @[json: 'memory_usage'] // Memory usage in bytes - cpu_usage f32 @[json: 'cpu_usage'] // CPU usage as a percentage (0-100) - children []ChildStats @[json: 'children'] // Stats for child processes -} - -// ChildStats represents statistics for a child process -pub struct ChildStats { -pub mut: - pid u32 @[json: 'pid'] // Process ID of the child process - memory_usage u64 @[json: 'memory_usage'] // Memory usage in bytes - cpu_usage f32 @[json: 'cpu_usage'] // CPU usage as a percentage (0-100) -} - -// ServiceCreateParams represents parameters for service_create method -pub struct ServiceCreateParams { -pub mut: - name string @[json: 'name'] // Name of the service to create - content ServiceConfig @[json: 'content'] // Configuration for the service -} - -// ServiceKillParams represents parameters for service_kill method -pub struct ServiceKillParams { -pub mut: - name string @[json: 'name'] // Name of the service to kill - signal string @[json: 'signal'] // Signal to send (e.g., SIGTERM, SIGKILL) -} - -// LogParams represents parameters for log streaming methods -@[params] -pub struct LogParams { -pub mut: - name string // Optional service name filter -} - -/////////////NORMALLY NO NEED TO TOUCH - -pub fn heroscript_dumps(obj ZinitRPC) !string { - return encoderhero.encode[ZinitRPC](obj)! -} - -pub fn heroscript_loads(heroscript string) !ZinitRPC { - mut obj := encoderhero.decode[ZinitRPC](heroscript)! - return obj -} - -// Factory function to create a new ZinitRPC client instance -@[params] -pub struct NewClientArgs { -pub mut: - name string = 'default' - socket_path string = default_socket_path -} - -pub fn new_client(args NewClientArgs) !&ZinitRPC { - mut client := ZinitRPC{ - name: args.name - socket_path: args.socket_path - } - client = obj_init(client)! - return &client -} diff --git a/lib/core/base/context.v b/lib/core/base/context.v index 2d22d583..0ed72b81 100644 --- a/lib/core/base/context.v +++ b/lib/core/base/context.v @@ -2,25 +2,16 @@ module base import freeflowuniverse.herolib.data.paramsparser import freeflowuniverse.herolib.core.redisclient -import freeflowuniverse.herolib.data.dbfs -import freeflowuniverse.herolib.crypt.aes_symmetric -import freeflowuniverse.herolib.ui -import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.core.texttools -import freeflowuniverse.herolib.core.rootpath import json import os -import crypto.md5 @[heap] pub struct Context { mut: - // priv_key_ ?&secp256k1.Secp256k1 @[skip; str: skip] - params_ ?¶msparser.Params - dbcollection_ ?&dbfs.DBCollection @[skip; str: skip] - redis_ ?&redisclient.Redis @[skip; str: skip] - path_ ?pathlib.Path + params_ ?¶msparser.Params + redis_ ?&redisclient.Redis @[skip; str: skip] + path_ ?pathlib.Path pub mut: // snippets map[string]string config ContextConfig @@ -34,10 +25,10 @@ pub mut: params string coderoot string interactive bool - secret string // is hashed secret - priv_key string // encrypted version - db_path string // path to dbcollection - encrypt bool + // secret string // is hashed secret + // priv_key string // encrypted version + // db_path string // path to dbcollection + // encrypt bool } // return the gistructure as is being used in context @@ -80,8 +71,6 @@ pub fn (mut self Context) redis() !&redisclient.Redis { pub fn (mut self Context) save() ! { jsonargs := json.encode_pretty(self.config) mut r := self.redis()! - // console.print_debug("save") - // console.print_debug(jsonargs) r.set('context:config', jsonargs)! } @@ -89,8 +78,6 @@ pub fn (mut self Context) save() ! { pub fn (mut self Context) load() ! { mut r := self.redis()! d := r.get('context:config')! - // console.print_debug("load") - // console.print_debug(d) if d.len > 0 { self.config = json.decode(ContextConfig, d)! } @@ -101,96 +88,42 @@ fn (mut self Context) cfg_redis_exists() !bool { return r.exists('context:config')! } -// return db collection -pub fn (mut self Context) dbcollection() !&dbfs.DBCollection { - mut dbc2 := self.dbcollection_ or { - if self.config.db_path.len == 0 { - self.config.db_path = '${os.home_dir()}/hero/db/${self.config.id}' - } - mut dbc := dbfs.get( - contextid: self.config.id - dbpath: self.config.db_path - secret: self.config.secret - )! - self.dbcollection_ = &dbc - &dbc - } +// pub fn (mut self Context) secret_encrypt(txt string) !string { +// return aes_symmetric.encrypt_str(txt, self.secret_get()!) +// } - return dbc2 -} +// pub fn (mut self Context) secret_decrypt(txt string) !string { +// return aes_symmetric.decrypt_str(txt, self.secret_get()!) +// } -pub fn (mut self Context) db_get(dbname string) !dbfs.DB { - mut dbc := self.dbcollection()! - return dbc.db_get_create(name: dbname, withkeys: true)! -} +// pub fn (mut self Context) secret_get() !string { +// mut secret := self.config.secret +// if secret == '' { +// self.secret_configure()! +// secret = self.config.secret +// self.save()! +// } +// if secret == '' { +// return error("can't get secret") +// } +// return secret +// } -// always return the config db which is the same for all apps in context -pub fn (mut self Context) db_config_get() !dbfs.DB { - mut dbc := self.dbcollection()! - return dbc.db_get_create(name: 'config', withkeys: true)! -} +// // show a UI in console to configure the secret +// pub fn (mut self Context) secret_configure() ! { +// mut myui := ui.new()! +// console.clear() +// secret_ := myui.ask_question(question: 'Please enter your hero secret string:')! +// self.secret_set(secret_)! +// } -pub fn (mut self Context) hero_config_set(cat string, name string, content_ string) ! { - mut content := texttools.dedent(content_) - content = rootpath.shell_expansion(content) - path := '${self.path()!.path}/${cat}__${name}.yaml' - mut config_file := pathlib.get_file(path: path)! - config_file.write(content)! -} - -pub fn (mut self Context) hero_config_delete(cat string, name string) ! { - path := '${self.path()!.path}/${cat}__${name}.yaml' - mut config_file := pathlib.get_file(path: path)! - config_file.delete()! -} - -pub fn (mut self Context) hero_config_exists(cat string, name string) bool { - path := '${os.home_dir()}/hero/context/${self.config.name}/${cat}__${name}.yaml' - return os.exists(path) -} - -pub fn (mut self Context) hero_config_get(cat string, name string) !string { - path := '${self.path()!.path}/${cat}__${name}.yaml' - mut config_file := pathlib.get_file(path: path, create: false)! - return config_file.read()! -} - -pub fn (mut self Context) secret_encrypt(txt string) !string { - return aes_symmetric.encrypt_str(txt, self.secret_get()!) -} - -pub fn (mut self Context) secret_decrypt(txt string) !string { - return aes_symmetric.decrypt_str(txt, self.secret_get()!) -} - -pub fn (mut self Context) secret_get() !string { - mut secret := self.config.secret - if secret == '' { - self.secret_configure()! - secret = self.config.secret - self.save()! - } - if secret == '' { - return error("can't get secret") - } - return secret -} - -// show a UI in console to configure the secret -pub fn (mut self Context) secret_configure() ! { - mut myui := ui.new()! - console.clear() - secret_ := myui.ask_question(question: 'Please enter your hero secret string:')! - self.secret_set(secret_)! -} - -// unhashed secret -pub fn (mut self Context) secret_set(secret_ string) ! { - secret := secret_.trim_space() - secret2 := md5.hexhash(secret) - self.config.secret = secret2 - self.save()! -} +// // unhashed secret +// pub fn (mut self Context) secret_set(secret_ string) ! { +// secret := secret_.trim_space() +// secret2 := md5.hexhash(secret) +// self.config.secret = secret2 +// self.save()! +// } pub fn (mut self Context) path() !pathlib.Path { return self.path_ or { diff --git a/lib/core/base/factory_context.v b/lib/core/base/factory_context.v index 6edd7d4b..f90d5c08 100644 --- a/lib/core/base/factory_context.v +++ b/lib/core/base/factory_context.v @@ -36,30 +36,12 @@ pub fn context_new(args_ ContextConfigArgs) !&Context { params: args_.params coderoot: args_.coderoot interactive: args_.interactive - secret: args_.secret - encrypt: args_.encrypt - } - - if args.encrypt && args.secret == '' && args.interactive { - mut myui := ui.new()! - console.clear() - args.secret = myui.ask_question(question: 'Please enter your hero secret string:')! - } - - if args.encrypt && args.secret.len > 0 { - args.secret = md5.hexhash(args.secret) } mut c := Context{ config: args } - // if args_.priv_key_hex.len > 0 { - // c.privkey_set(args_.priv_key_hex)! - // } - - // c.save()! - if args.params.len > 0 { mut p := paramsparser.new('')! c.params_ = &p diff --git a/lib/core/base/session.v b/lib/core/base/session.v index 1ff7c042..1e5c1e94 100644 --- a/lib/core/base/session.v +++ b/lib/core/base/session.v @@ -3,7 +3,6 @@ module base import freeflowuniverse.herolib.data.ourtime // import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.data.paramsparser -import freeflowuniverse.herolib.data.dbfs import freeflowuniverse.herolib.core.logger import json import freeflowuniverse.herolib.core.pathlib @@ -32,16 +31,6 @@ pub mut: // return 'hero:sessions:${self.guid()}' // } -// get db of the session, is unique per session -pub fn (mut self Session) db_get() !dbfs.DB { - return self.context.db_get('session_${self.name}')! -} - -// get the db of the config, is unique per context -pub fn (mut self Session) db_config_get() !dbfs.DB { - return self.context.db_get('config')! -} - // load the params from redis pub fn (mut self Session) load() ! { mut r := self.context.redis()! diff --git a/lib/core/generator/generic/templates/objname_actions.vtemplate b/lib/core/generator/generic/templates/objname_actions.vtemplate index 45ee85ea..be9fb5b2 100644 --- a/lib/core/generator/generic/templates/objname_actions.vtemplate +++ b/lib/core/generator/generic/templates/objname_actions.vtemplate @@ -7,7 +7,7 @@ import freeflowuniverse.herolib.core.pathlib @if args.startupmanager import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager @end import freeflowuniverse.herolib.installers.ulist @@ -20,11 +20,11 @@ import freeflowuniverse.herolib.installers.lang.python import os @if args.startupmanager -fn startupcmd () ![]zinit.ZProcessNewArgs{ +fn startupcmd () ![]startupmanager.ZProcessNewArgs{ mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: '${args.name}' // cmd: '${args.name} server' // env: { diff --git a/lib/core/generator/generic/templates/objname_factory_.vtemplate b/lib/core/generator/generic/templates/objname_factory_.vtemplate index 37e1ca5e..3c070a1a 100644 --- a/lib/core/generator/generic/templates/objname_factory_.vtemplate +++ b/lib/core/generator/generic/templates/objname_factory_.vtemplate @@ -5,10 +5,10 @@ import freeflowuniverse.herolib.core.base @end import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json @if args.cat == .installer import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit @if args.startupmanager import time @end @@ -21,93 +21,147 @@ __global ( /////////FACTORY -^^[params] -pub struct ArgsGet{ +@if args.hasconfig +@@[params] +pub struct ArgsGet { pub mut: - name string + name string = "default" + fromdb bool //will load from filesystem + create bool //default will not create if not exist } +@else +@@[params] +pub struct ArgsGet { +pub mut: + name string = "default" +} +@end @if args.hasconfig -fn args_get (args_ ArgsGet) ArgsGet { - mut args:=args_ - if args.name == ""{ - args.name = "default" - } - return args +pub fn new(args ArgsGet) !&${args.classname} { + mut obj := ${args.classname}{ + name: args.name + } + set(obj)! + return get(name:args.name)! } -pub fn get(args_ ArgsGet) !&${args.classname} { - mut context:=base.context()! - mut args := args_get(args_) - mut obj := ${args.classname}{name:args.name} - if !(args.name in ${args.name}_global) { - if ! exists(args)!{ - set(obj)! - }else{ - heroscript := context.hero_config_get("${args.name}",args.name)! - mut obj_:=heroscript_loads(heroscript)! - set_in_mem(obj_)! - } +pub fn get(args ArgsGet) !&${args.classname} { + mut context := base.context()! + ${args.name}_default = args.name + if args.fromdb || args.name !in ${args.name}_global { + mut r := context.redis()! + if r.hexists('context:${args.name}', args.name)! { + data := r.hget('context:${args.name}', args.name)! + if data.len == 0 { + return error('${args.classname} with name: ${args.name} does not exist, prob bug.') + } + mut obj := json.decode(${args.classname},data)! + set_in_mem(obj)! + }else{ + if args.create { + new(args)! + }else{ + return error("${args.classname} with name '${args.name}' does not exist") + } + } + return get(name: args.name)! //no longer from db nor create } return ${args.name}_global[args.name] or { - println(${args.name}_global) - //bug if we get here because should be in globals - panic("could not get config for ${args.name} with name, is bug:??{args.name}") - } + return error('could not get config for ${args.name} with name:${args.name}') + } } -//register the config for the future -pub fn set(o ${args.classname})! { - set_in_mem(o)! - mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set("${args.name}", o.name, heroscript)! +// register the config for the future +pub fn set(o ${args.classname}) ! { + mut o2:=set_in_mem(o)! + ${args.name}_default = o2.name + mut context := base.context()! + mut r := context.redis()! + r.hset('context:${args.name}', o2.name, json.encode(o2))! } -//does the config exists? -pub fn exists(args_ ArgsGet)! bool { - mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists("${args.name}", args.name) +// does the config exists? +pub fn exists(args ArgsGet) !bool { + mut context := base.context()! + mut r := context.redis()! + return r.hexists('context:${args.name}', args.name)! } -pub fn delete(args_ ArgsGet)! { - mut args := args_get(args_) - mut context:=base.context()! - context.hero_config_delete("${args.name}",args.name)! - if args.name in ${args.name}_global { - //del ${args.name}_global[args.name] - } +pub fn delete(args ArgsGet) ! { + mut context := base.context()! + mut r := context.redis()! + r.hdel('context:${args.name}', args.name)! } -//only sets in mem, does not set as config -fn set_in_mem(o ${args.classname})! { - mut o2:=obj_init(o)! - ${args.name}_global[o.name] = &o2 - ${args.name}_default = o.name +@@[params] +pub struct ArgsList { +pub mut: + fromdb bool //will load from filesystem } +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&${args.classname} { + mut res := []&${args.classname}{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + ${args.name}_global = map[string]&${args.classname}{} + ${args.name}_default = '' + } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:${args.name}')! + + for name in l{ + res << get(name:name,fromdb:true)! + } + return res + } else { + // load from memory + for _, client in ${args.name}_global { + res << client + } + } + return res +} + + +// only sets in mem, does not set as config +fn set_in_mem(o ${args.classname}) ! ${args.classname} { + mut o2 := obj_init(o)! + ${args.name}_global[o2.name] = &o2 + ${args.name}_default = o2.name + return o2 +} + + @else -pub fn get(args_ ArgsGet) !&${args.classname} { - return &${args.classname}{} +pub fn new(args ArgsGet) !&${args.classname} { + return &${args.classname}{} +} +pub fn get(args ArgsGet) !&${args.classname} { + return new(args)! } @end pub fn play(mut plbook PlayBook) ! { - - - @if args.hasconfig - mut install_actions := plbook.find(filter: '${args.name}.configure')! - if install_actions.len > 0 { - for install_action in install_actions { - heroscript:=install_action.heroscript() - mut obj2:=heroscript_loads(heroscript)! - set(obj2)! - } + if ! plbook.exists(filter: '${args.name}.'){ + return } - @end - + mut install_actions := plbook.find(filter: '${args.name}.configure')! + if install_actions.len > 0 { + @if args.hasconfig + for install_action in install_actions { + heroscript := install_action.heroscript() + mut obj2 := heroscript_loads(heroscript)! + set(obj2)! + } + @else + return error("can't configure ${args.name}, because no configuration allowed for this installer.") + @end + } @if args.cat == .installer mut other_actions := plbook.find(filter: '${args.name}.')! for other_action in other_actions { @@ -145,48 +199,58 @@ pub fn play(mut plbook PlayBook) ! { } @end } - @end - + @end } + @if args.cat == .installer //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +@if args.startupmanager +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux - // systemd + // systemd match cat{ + .screen { + console.print_debug("startupmanager: screen") + return startupmanager.get(.screen)! + } .zinit{ console.print_debug("startupmanager: zinit") - return startupmanager.get(cat:.zinit)! + return startupmanager.get(.zinit)! } .systemd{ console.print_debug("startupmanager: systemd") - return startupmanager.get(cat:.systemd)! + return startupmanager.get(.systemd)! }else{ console.print_debug("startupmanager: auto") - return startupmanager.get()! + return startupmanager.get(.auto)! } } } +@end @if args.hasconfig //load from disk and make sure is properly intialized pub fn (mut self ${args.classname}) reload() ! { + @if ! args.singleton switch(self.name) + @end self=obj_init(self)! } @end @if args.startupmanager pub fn (mut self ${args.classname}) start() ! { + @if ! args.singleton switch(self.name) + @end if self.running()!{ return } @@ -250,10 +314,12 @@ pub fn (mut self ${args.classname}) running() !bool { //walk over the generic processes, if not running return for zprocess in startupcmd()!{ - mut sm:=startupmanager_get(zprocess.startuptype)! - r:=sm.running(zprocess.name)! - if r==false{ - return false + if zprocess.startuptype != .screen{ + mut sm:=startupmanager_get(zprocess.startuptype)! + r:=sm.running(zprocess.name)! + if r==false{ + return false + } } } return running()! @@ -291,15 +357,9 @@ pub fn (mut self ${args.classname}) destroy() ! { @end -//switch instance to be used for ${args.name} +// switch instance to be used for ${args.name} pub fn switch(name string) { +@if ! args.singleton ${args.name}_default = name +@end } - - -//helpers - -^^[params] -pub struct DefaultConfigArgs{ - instance string = 'default' -} \ No newline at end of file diff --git a/lib/core/generator/generic/templates/objname_model.vtemplate b/lib/core/generator/generic/templates/objname_model.vtemplate index 5e07144f..9933e197 100644 --- a/lib/core/generator/generic/templates/objname_model.vtemplate +++ b/lib/core/generator/generic/templates/objname_model.vtemplate @@ -69,10 +69,6 @@ fn configure() ! { /////////////NORMALLY NO NEED TO TOUCH -pub fn heroscript_dumps(obj ${args.classname}) !string { - return encoderhero.encode[${args.classname} ](obj)! -} - pub fn heroscript_loads(heroscript string) !${args.classname} { mut obj := encoderhero.decode[${args.classname}](heroscript)! return obj diff --git a/lib/core/generator/installer_client_OLD/ask.v b/lib/core/generator/installer_client_OLD/ask.v deleted file mode 100644 index ff0d6bde..00000000 --- a/lib/core/generator/installer_client_OLD/ask.v +++ /dev/null @@ -1,69 +0,0 @@ -module installer_client - -import freeflowuniverse.herolib.ui.console -import os -import freeflowuniverse.herolib.core.pathlib - -// will ask questions & create the .heroscript -pub fn ask(path string) ! { - mut myconsole := console.new() - - mut model := gen_model_get(path, false)! - - console.clear() - console.print_header('Configure generation of code for a module on path:') - console.print_green('Path: ${path}') - console.lf() - - model.classname = myconsole.ask_question( - description: 'Class name of the ${model.cat}' - question: 'What is the class name of the generator e.g. MyClass ?' - warning: 'Please provide a valid class name for the generator' - default: model.classname - minlen: 4 - )! - - model.title = myconsole.ask_question( - description: 'Title of the ${model.cat} (optional)' - default: model.title - )! - - model.hasconfig = !myconsole.ask_yesno( - description: 'Is there a config (normally yes)?' - default: model.hasconfig - )! - - if model.hasconfig { - model.singleton = !myconsole.ask_yesno( - description: 'Can there be multiple instances (normally yes)?' - default: !model.singleton - )! - if model.cat == .installer { - model.templates = myconsole.ask_yesno( - description: 'Will there be templates available for your installer?' - default: model.templates - )! - } - } else { - model.singleton = true - } - - if model.cat == .installer { - model.startupmanager = myconsole.ask_yesno( - description: 'Is this an installer which will be managed by a startup mananger?' - default: model.startupmanager - )! - - model.build = myconsole.ask_yesno( - description: 'Are there builders for the installers (compilation)' - default: model.build - )! - } - - // if true{ - // println(model) - // panic("Sdsd") - // } - - gen_model_set(GenerateArgs{ model: model, path: path })! -} diff --git a/lib/core/generator/installer_client_OLD/factory.v b/lib/core/generator/installer_client_OLD/factory.v deleted file mode 100644 index db7c2a08..00000000 --- a/lib/core/generator/installer_client_OLD/factory.v +++ /dev/null @@ -1,85 +0,0 @@ -module installer_client - -import freeflowuniverse.herolib.ui.console -import os - -@[params] -pub struct GenerateArgs { -pub mut: - reset bool // regenerate all, dangerous !!! - interactive bool // if we want to ask - path string - playonly bool - model ?GenModel - cat ?Cat -} - -pub struct PlayArgs { -pub mut: - name string - modulepath string -} - -// the default to start with -// -// reset bool // regenerate all, dangerous !!! -// interactive bool //if we want to ask -// path string -// model ?GenModel -// cat ?Cat -// -// will return the module path where we need to execute a play command as well as the name of -pub fn do(args_ GenerateArgs) !PlayArgs { - mut args := args_ - - console.print_header('Generate code for path: ${args.path} (reset:${args.reset}, interactive:${args.interactive})') - - mut create := true // to create .heroscript - - mut model := args.model or { - create = false // we cannot create because model not given - if args.path == '' { - args.path = os.getwd() - } - mut m := gen_model_get(args.path, false)! - m - } - - if model.classname == '' { - args.interactive = true - } - - if create { - if args.path == '' { - return error('need to specify path fo ${args_} because we asked to create .heroscript ') - } - gen_model_set(args)! // persist it on disk - } else { - if args.path == '' { - args.path = os.getwd() - } - } - - // if model.cat == .unknown { - // model.cat = args.cat or { return error('cat needs to be specified for generator.') } - // } - - if args.interactive { - ask(args.path)! - args.model = gen_model_get(args.path, false)! - } else { - args.model = model - } - - console.print_debug(args) - - // only generate if playonly is false and there is a classname - if !args.playonly && model.classname.len > 0 { - generate(args)! - } - - return PlayArgs{ - name: model.play_name - modulepath: model.module_path - } -} diff --git a/lib/core/generator/installer_client_OLD/generate.v b/lib/core/generator/installer_client_OLD/generate.v deleted file mode 100644 index 102e692f..00000000 --- a/lib/core/generator/installer_client_OLD/generate.v +++ /dev/null @@ -1,77 +0,0 @@ -module installer_client - -import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.core.pathlib - -// generate based on filled in args, ask has to be done before -fn generate(args GenerateArgs) ! { - console.print_debug('generate code for path: ${args.path}') - - // as used in the templates - model := args.model or { panic('bug no model specified in generate') } - - mut path_actions := pathlib.get(args.path + '/${model.name}_actions.v') - if args.reset { - path_actions.delete()! - } - if !path_actions.exists() && model.cat == .installer { - console.print_debug('write installer actions') - mut templ_1 := $tmpl('templates/objname_actions.vtemplate') - pathlib.template_write(templ_1, '${args.path}/${model.name}_actions.v', true)! - } - - mut templ_2 := $tmpl('templates/objname_factory_.vtemplate') - - pathlib.template_write(templ_2, '${args.path}/${model.name}_factory_.v', true)! - - mut path_model := pathlib.get(args.path + '/${model.name}_model.v') - if args.reset || !path_model.exists() { - console.print_debug('write model.') - mut templ_3 := $tmpl('templates/objname_model.vtemplate') - pathlib.template_write(templ_3, '${args.path}/${model.name}_model.v', true)! - } - - // TODO: check case sensistivity for delete - mut path_readme := pathlib.get(args.path + '/readme.md') - if args.reset || !path_readme.exists() { - mut templ_readme := $tmpl('templates/readme.md') - pathlib.template_write(templ_readme, '${args.path}/readme.md', true)! - } - - mut path_templ_dir := pathlib.get_dir(path: args.path + '/templates', create: false)! - if args.reset { - path_templ_dir.delete()! - } - if (args.model or { panic('bug') }).templates { - if !path_templ_dir.exists() { - mut templ_6 := $tmpl('templates/atemplate.yaml') - pathlib.template_write(templ_6, '${args.path}/templates/atemplate.yaml', true)! - } - } -} - -// fn platform_check(args GenModel) ! { -// ok := 'osx,ubuntu,arch' -// ok2 := ok.split(',') -// for i in args.supported_platforms { -// if i !in ok2 { -// return error('cannot find ${i} in choices for supported_platforms. Valid ones are ${ok}') -// } -// } -// } - -// pub fn (args GenModel) platform_check_str() string { -// mut out := '' - -// if 'osx' in args.supported_platforms { -// out += 'myplatform == .osx || ' -// } -// if 'ubuntu' in args.supported_platforms { -// out += 'myplatform == .ubuntu ||' -// } -// if 'arch' in args.supported_platforms { -// out += 'myplatform == .arch ||' -// } -// out = out.trim_right('|') -// return out -// } diff --git a/lib/core/generator/installer_client_OLD/model.v b/lib/core/generator/installer_client_OLD/model.v deleted file mode 100644 index 623c92d0..00000000 --- a/lib/core/generator/installer_client_OLD/model.v +++ /dev/null @@ -1,136 +0,0 @@ -module installer_client - -import os -import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.core.playbook -import freeflowuniverse.herolib.ui.console - -pub struct GenModel { -pub mut: - name string - classname string - default bool = true // means user can just get the object and a default will be created - title string - // supported_platforms []string // only relevant for installers for now - singleton bool // means there can only be one - templates bool // means we will use templates in the installer, client doesn't do this' - reset bool // regenerate all, dangerous !!! - interactive bool // if we want to ask - startupmanager bool = true - build bool = true - hasconfig bool = true - cat Cat // dont' set default - play_name string // e.g. docusaurus is what we look for - module_path string // e.g.freeflowuniverse.herolib.web.docusaurus -} - -pub enum Cat { - unknown - client - installer -} - -// creates the heroscript from the GenModel as part of GenerateArgs -pub fn gen_model_set(args GenerateArgs) ! { - console.print_debug('Code generator set: ${args}') - model := args.model or { return error('model is none') } - heroscript_templ := match model.cat { - .client { $tmpl('templates/heroscript_client') } - .installer { $tmpl('templates/heroscript_installer') } - else { return error('Invalid category: ${model.cat}') } - } - pathlib.template_write(heroscript_templ, '${args.path}/.heroscript', true)! -} - -// loads the heroscript and return the model -pub fn gen_model_get(path string, create bool) !GenModel { - console.print_debug('play installer code for path: ${path}') - - mut config_path := pathlib.get_file(path: '${path}/.heroscript', create: create)! - - mut plbook := playbook.new(text: config_path.read()!)! - - mut model := GenModel{} - mut found := false - - mut install_actions := plbook.find(filter: 'hero_code.generate_installer')! - if install_actions.len > 0 { - for install_action in install_actions { - if found { - return error('cannot find more than one her_code.generate_installer ... in ${path}') - } - found = true - mut p := install_action.params - model = GenModel{ - name: p.get_default('name', '')! - classname: p.get_default('classname', '')! - title: p.get_default('title', '')! - default: p.get_default_true('default') - // supported_platforms: p.get_list('supported_platforms')! - singleton: p.get_default_false('singleton') - templates: p.get_default_false('templates') - startupmanager: p.get_default_true('startupmanager') - build: p.get_default_true('build') - hasconfig: p.get_default_true('hasconfig') - cat: .installer - } - } - } - - mut client_actions := plbook.find(filter: 'hero_code.generate_client')! - if client_actions.len > 0 { - for client_action in client_actions { - if found { - return error('cannot find more than one her_code.generate_client ... in ${path}') - } - found = true - mut p := client_action.params - model = GenModel{ - name: p.get_default('name', '')! - classname: p.get_default('classname', '')! - title: p.get_default('title', '')! - default: p.get_default_true('default') - singleton: p.get_default_false('singleton') - hasconfig: p.get_default_true('hasconfig') - cat: .client - } - } - } - - if model.cat == .unknown { - if path.contains('clients') { - model.cat = .client - } else { - model.cat = .installer - } - } - - if model.name == '' { - model.name = os.base(path).to_lower() - } - - model.play_name = model.name - - pathsub := path.replace('${os.home_dir()}/code/github/', '') - model.module_path = pathsub.replace('/', '.').replace('.lib.', '.') - - // !!hero_code.play - // name:'docusaurus' - - mut play_actions := plbook.find(filter: 'hero_code.play')! - if play_actions.len > 1 { - return error('should have max 1 hero_code.play action in ${config_path.path}') - } - if play_actions.len == 1 { - mut p := play_actions[0].params - model.play_name = p.get_default('name', model.name)! - } - - if model.module_path.contains('docusaurus') { - println(model) - println('4567ujhjk') - exit(0) - } - - return model -} diff --git a/lib/core/generator/installer_client_OLD/readme.md b/lib/core/generator/installer_client_OLD/readme.md deleted file mode 100644 index cf259770..00000000 --- a/lib/core/generator/installer_client_OLD/readme.md +++ /dev/null @@ -1,71 +0,0 @@ -# generation framework for clients & installers - -```bash -#generate all play commands -hero generate -playonly -#will ask questions if .heroscript is not there yet -hero generate -p thepath_is_optional -# to generate without questions -hero generate -p thepath_is_optional -t client -#if installer, default is a client -hero generate -p thepath_is_optional -t installer - -#when you want to scan over multiple directories -hero generate -p thepath_is_optional -t installer -s - -``` - -there will be a ```.heroscript``` in the director you want to generate for, the format is as follows: - -```hero -//for a server -!!hero_code.generate_installer - name:'daguserver' - classname:'DaguServer' - singleton:1 //there can only be 1 object in the globals, is called 'default' - templates:1 //are there templates for the installer - title:'' - startupmanager:1 //managed by a startup manager, default true - build:1 //will we also build the component - -//or for a client - -!!hero_code.generate_client - name:'mail' - classname:'MailClient' - singleton:0 //default is 0 - -``` - -needs to be put as .heroscript in the directories which we want to generate - - -## templates remarks - -in templates: - -- ^^ or @@ > gets replaced to @ -- ?? > gets replaced to $ - -this is to make distinction between processing at compile time (pre-compile) or at runtime. - -## call by code - -to call in code - -```v -#!/usr/bin/env -S v -gc none -cc tcc -d use_openssl -enable-globals run - -import freeflowuniverse.herolib.code.generator.generic - -generic.scan(path:"~/code/github/freeflowuniverse/herolib/herolib/installers",force:true)! - - -``` - -to run from bash - -```bash -~/code/github/freeflowuniverse/herolib/scripts/fix_installers.vsh -``` - diff --git a/lib/core/generator/installer_client_OLD/scanner.v b/lib/core/generator/installer_client_OLD/scanner.v deleted file mode 100644 index 642135fe..00000000 --- a/lib/core/generator/installer_client_OLD/scanner.v +++ /dev/null @@ -1,49 +0,0 @@ -module installer_client - -import os -import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.ui.console - -@[params] -pub struct ScannerArgs { -pub mut: - reset bool // regenerate all, dangerous !!! - interactive bool // if we want to ask - path string - playonly bool -} - -// scan over a set of directories call the play where -pub fn scan(args ScannerArgs) ! { - console.print_debug('Code generator scan: ${args.path}') - - if args.path == '' { - scan(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/installers')! - scan(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/clients')! - scan(path: '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/web')! - return - } - - console.print_header('Scan for generation of code for ${args.path}') - - // now walk over all directories, find .heroscript - mut pathroot := pathlib.get_dir(path: args.path, create: false)! - mut plist := pathroot.list( - recursive: true - ignoredefault: false - regex: ['.heroscript'] - )! - - for mut p in plist.paths { - pparent := p.parent()! - path_module := pparent.path - if os.exists('${path_module}/.heroscript') { - do( - interactive: args.interactive - path: path_module - reset: args.reset - playonly: args.playonly - )! - } - } -} diff --git a/lib/core/generator/installer_client_OLD/templates/atemplate.yaml b/lib/core/generator/installer_client_OLD/templates/atemplate.yaml deleted file mode 100644 index 07ae0536..00000000 --- a/lib/core/generator/installer_client_OLD/templates/atemplate.yaml +++ /dev/null @@ -1,5 +0,0 @@ - - -name: ??{model.name} - - diff --git a/lib/core/generator/installer_client_OLD/templates/heroscript_client b/lib/core/generator/installer_client_OLD/templates/heroscript_client deleted file mode 100644 index 4b55af3d..00000000 --- a/lib/core/generator/installer_client_OLD/templates/heroscript_client +++ /dev/null @@ -1,7 +0,0 @@ -!!hero_code.generate_client - name: "${model.name}" - classname: "${model.classname}" - hasconfig: ${model.hasconfig} - singleton: ${model.singleton} - default: ${model.default} - title: "${model.title}" diff --git a/lib/core/generator/installer_client_OLD/templates/heroscript_installer b/lib/core/generator/installer_client_OLD/templates/heroscript_installer deleted file mode 100644 index 25bb3472..00000000 --- a/lib/core/generator/installer_client_OLD/templates/heroscript_installer +++ /dev/null @@ -1,11 +0,0 @@ -!!hero_code.generate_installer - name: "${model.name}" - classname: "${model.classname}" - hasconfig: ${model.hasconfig} - singleton: ${model.singleton} - default: ${model.default} - title: "${model.title}" - templates: ${model.templates} - build: ${model.build} - startupmanager: ${model.startupmanager} - diff --git a/lib/core/generator/installer_client_OLD/templates/objname_actions.vtemplate b/lib/core/generator/installer_client_OLD/templates/objname_actions.vtemplate deleted file mode 100644 index 1b900c97..00000000 --- a/lib/core/generator/installer_client_OLD/templates/objname_actions.vtemplate +++ /dev/null @@ -1,219 +0,0 @@ -module ${model.name} - -import freeflowuniverse.herolib.osal.core as osal -import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.core.texttools -import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.core -import freeflowuniverse.herolib.installers.ulist -import freeflowuniverse.herolib.installers.base - -@if model.startupmanager -import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit -@end - -@if model.build -import freeflowuniverse.herolib.installers.lang.golang -import freeflowuniverse.herolib.installers.lang.rust -import freeflowuniverse.herolib.installers.lang.python -@end - -import os - -@if model.startupmanager -fn startupcmd () ![]zinit.ZProcessNewArgs{ - mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} - //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ - // name: '${model.name}' - // cmd: '${model.name} server' - // env: { - // 'HOME': '/root' - // } - // } - - return res - -} - -fn running_() !bool { - mut installer := get()! - //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // this checks health of ${model.name} - // curl http://localhost:3333/api/v1/s --oauth2-bearer 1234 works - // url:='http://127.0.0.1:??{cfg.port}/api/v1' - // mut conn := httpconnection.new(name: '${model.name}', url: url)! - - // if cfg.secret.len > 0 { - // conn.default_header.add(.authorization, 'Bearer ??{cfg.secret}') - // } - // conn.default_header.add(.content_type, 'application/json') - // console.print_debug("curl -X 'GET' '??{url}'/tags --oauth2-bearer ??{cfg.secret}") - // r := conn.get_json_dict(prefix: 'tags', debug: false) or {return false} - // println(r) - // if true{panic("ssss")} - // tags := r['Tags'] or { return false } - // console.print_debug(tags) - // console.print_debug('${model.name} is answering.') - return false -} - -fn start_pre()!{ - -} - -fn start_post()!{ - -} - -fn stop_pre()!{ - -} - -fn stop_post()!{ - -} - -@end - -//////////////////// following actions are not specific to instance of the object - -@if model.cat == .installer -// checks if a certain version or above is installed -fn installed_() !bool { - //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res := os.execute('??{osal.profile_path_source_and()!} ${model.name} version') - // if res.exit_code != 0 { - // return false - // } - // r := res.output.split_into_lines().filter(it.trim_space().len > 0) - // if r.len != 1 { - // return error("couldn't parse ${model.name} version.\n??{res.output}") - // } - // if texttools.version(version) == texttools.version(r[0]) { - // return true - // } - return false -} - -//get the Upload List of the files -fn ulist_get() !ulist.UList { - //optionally build a UList which is all paths which are result of building, is then used e.g. in upload - return ulist.UList{} -} - -//uploads to S3 server if configured -fn upload_() ! { - // installers.upload( - // cmdname: '${model.name}' - // source: '??{gitpath}/target/x86_64-unknown-linux-musl/release/${model.name}' - // )! - -} - -fn install_() ! { - console.print_header('install ${model.name}') - //THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // mut url := '' - // if core.is_linux_arm()! { - // url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_linux_arm64.tar.gz' - // } else if core.is_linux_intel()! { - // url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_linux_amd64.tar.gz' - // } else if core.is_osx_arm()! { - // url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_darwin_arm64.tar.gz' - // } else if core.is_osx_intel()! { - // url = 'https://github.com/${model.name}-dev/${model.name}/releases/download/v??{version}/${model.name}_??{version}_darwin_amd64.tar.gz' - // } else { - // return error('unsported platform') - // } - - // mut dest := osal.download( - // url: url - // minsize_kb: 9000 - // expand_dir: '/tmp/${model.name}' - // )! - - // //dest.moveup_single_subdir()! - - // mut binpath := dest.file_get('${model.name}')! - // osal.cmd_add( - // cmdname: '${model.name}' - // source: binpath.path - // )! -} - -@if model.build -fn build_() ! { - //url := 'https://github.com/threefoldtech/${model.name}' - - // make sure we install base on the node - // if core.platform()!= .ubuntu { - // return error('only support ubuntu for now') - // } - - //mut g:=golang.get()! - //g.install()! - - //console.print_header('build coredns') - - //mut gs := gittools.new(coderoot: '~/code')! - // console.print_header('build ${model.name}') - - // gitpath := gittools.get_repo(url: url, reset: true, pull: true)! - - // cmd := ' - // cd ??{gitpath} - // source ~/.cargo/env - // exit 1 #todo - // ' - // osal.execute_stdout(cmd)! - // - // //now copy to the default bin path - // mut binpath := dest.file_get('...')! - // adds it to path - // osal.cmd_add( - // cmdname: 'griddriver2' - // source: binpath.path - // )! - -} -@end - -fn destroy_() ! { - - // mut systemdfactory := systemd.new()! - // systemdfactory.destroy("zinit")! - - // osal.process_kill_recursive(name:'zinit')! - // osal.cmd_delete('zinit')! - - // osal.package_remove(' - // podman - // conmon - // buildah - // skopeo - // runc - // ')! - - // //will remove all paths where go/bin is found - // osal.profile_path_add_remove(paths2delete:"go/bin")! - - // osal.rm(" - // podman - // conmon - // buildah - // skopeo - // runc - // /var/lib/containers - // /var/lib/podman - // /var/lib/buildah - // /tmp/podman - // /tmp/conmon - // ")! - - -} - -@end \ No newline at end of file diff --git a/lib/core/generator/installer_client_OLD/templates/objname_factory_.vtemplate b/lib/core/generator/installer_client_OLD/templates/objname_factory_.vtemplate deleted file mode 100644 index 3f7721b6..00000000 --- a/lib/core/generator/installer_client_OLD/templates/objname_factory_.vtemplate +++ /dev/null @@ -1,338 +0,0 @@ - -module ${model.name} - -import freeflowuniverse.herolib.core.base -import freeflowuniverse.herolib.core.playbook -import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.core -@if model.hasconfig -import freeflowuniverse.herolib.data.encoderhero -@end - -@if model.cat == .installer -import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit -import time -@end - -__global ( - ${model.name}_global map[string]&${model.classname} - ${model.name}_default string -) - -/////////FACTORY - - -@if model.singleton == false - -^^[params] -pub struct ArgsGet{ -pub mut: - name string -} - -fn args_get (args_ ArgsGet) ArgsGet { - mut model:=args_ - if model.name == ""{ - model.name = ${model.name}_default - } - if model.name == ""{ - model.name = "default" - } - return model -} - -pub fn get(args_ ArgsGet) !&${model.classname} { - mut args := args_get(args_) - if !(args.name in ${model.name}_global) { - if args.name=="default"{ - if ! exists(args)!{ - if default{ - mut context:=base.context() or { panic("bug") } - context.hero_config_set("${model.name}",args.name,heroscript_default()!)! - } - } - load(args)! - } - } - return ${model.name}_global[args.name] or { - println(${model.name}_global) - panic("could not get config for ??{args.name}.") - } -} - -@end - -@if model.hasconfig - -//set the model in mem and the config on the filesystem -pub fn set(o ${model.classname})! { - mut o2:=obj_init(o)! - ${model.name}_global[o.name] = &o2 - ${model.name}_default = o.name -} - -//check we find the config on the filesystem -pub fn exists(args_ ArgsGet)!bool { - mut model := args_get(args_) - mut context:=base.context()! - return context.hero_config_exists("${model.name}",model.name) -} - -//load the config error if it doesn't exist -pub fn load(args_ ArgsGet) ! { - mut model := args_get(args_) - mut context:=base.context()! - mut heroscript := context.hero_config_get("${model.name}",model.name)! - play(heroscript:heroscript)! -} - -//save the config to the filesystem in the context -pub fn save(o ${model.classname})! { - mut context:=base.context()! - heroscript := encoderhero.encode[${model.classname}](o)! - context.hero_config_set("${model.name}",o.name,heroscript)! -} - - -pub fn play(mut plbook PlayBook) ! { - - mut plbook := model.plbook or { - playbook.new(text: model.heroscript)! - } - - @if model.hasconfig - mut configure_actions := plbook.find(filter: '${model.name}.configure')! - if configure_actions.len > 0 { - for config_action in configure_actions { - mut p := config_action.params - mycfg:=cfg_play(p)! - console.print_debug("install action ${model.name}.configure\n??{mycfg}") - set(mycfg)! - save(mycfg)! - } - } - @end - - @if model.cat == .installer - mut other_actions := plbook.find(filter: '${model.name}.')! - for other_action in other_actions { - if other_action.name in ["destroy","install","build"]{ - mut p := other_action.params - reset:=p.get_default_false("reset") - if other_action.name == "destroy" || reset{ - console.print_debug("install action ${model.name}.destroy") - destroy_()! - } - if other_action.name == "install"{ - console.print_debug("install action ${model.name}.install") - install_()! - } - } - @if model.startupmanager - if other_action.name in ["start","stop","restart"]{ - mut p := other_action.params - name := p.get('name')! - mut ${model.name}_obj:=get(name:name)! - console.print_debug("action object:\n??{${model.name}_obj}") - if other_action.name == "start"{ - console.print_debug("install action ${model.name}.??{other_action.name}") - ${model.name}_obj.start()! - } - - if other_action.name == "stop"{ - console.print_debug("install action ${model.name}.??{other_action.name}") - ${model.name}_obj.stop()! - } - if other_action.name == "restart"{ - console.print_debug("install action ${model.name}.??{other_action.name}") - ${model.name}_obj.restart()! - } - } - @end - } - @end - -} - -@end - -@if model.cat == .installer - -//////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////// - - -@if model.hasconfig -//load from disk and make sure is properly intialized -pub fn (mut self ${model.classname}) reload() ! { - switch(self.name) - self=obj_init(self)! -} -@end - -@if model.startupmanager - -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat{ - .zinit{ - console.print_debug("startupmanager: zinit") - return startupmanager.get(cat:.zinit)! - } - .systemd{ - console.print_debug("startupmanager: systemd") - return startupmanager.get(cat:.systemd)! - }else{ - console.print_debug("startupmanager: auto") - return startupmanager.get()! - } - } -} - -pub fn (mut self ${model.classname}) start() ! { - switch(self.name) - if self.running()!{ - return - } - - console.print_header('${model.name} start') - - if ! installed_()!{ - install_()! - } - - configure()! - - start_pre()! - - for zprocess in startupcmd()!{ - mut sm:=startupmanager_get(zprocess.startuptype)! - - console.print_debug('starting ${model.name} with ??{zprocess.startuptype}...') - - sm.new(zprocess)! - - sm.start(zprocess.name)! - } - - start_post()! - - for _ in 0 .. 50 { - if self.running()! { - return - } - time.sleep(100 * time.millisecond) - } - return error('${model.name} did not install properly.') - -} - -pub fn (mut self ${model.classname}) install_start(model InstallArgs) ! { - switch(self.name) - self.install(model)! - self.start()! -} - -pub fn (mut self ${model.classname}) stop() ! { - switch(self.name) - stop_pre()! - for zprocess in startupcmd()!{ - mut sm:=startupmanager_get(zprocess.startuptype)! - sm.stop(zprocess.name)! - } - stop_post()! -} - -pub fn (mut self ${model.classname}) restart() ! { - switch(self.name) - self.stop()! - self.start()! -} - -pub fn (mut self ${model.classname}) running() !bool { - switch(self.name) - - //walk over the generic processes, if not running_ return - for zprocess in startupcmd()!{ - mut sm:=startupmanager_get(zprocess.startuptype)! - r:=sm.running(zprocess.name)! - if r==false{ - return false - } - } - return running_()! -} -@end - -@@[params] -pub struct InstallArgs{ -pub mut: - reset bool -} - -@if model.singleton - -pub fn install(args InstallArgs) ! { - if args.reset { - destroy()! - } - if ! (installed_()!){ - install_()! - } -} - -pub fn destroy() ! { - destroy_()! -} - -@if model.build -pub fn build() ! { - build_()! -} -@end - - -@else - -//switch instance to be used for ${model.name} -pub fn switch(name string) { - ${model.name}_default = name -} - - -pub fn (mut self ${model.classname}) install(args InstallArgs) ! { - switch(self.name) - if args.reset { - destroy_()! - } - if ! (installed_()!){ - install_()! - } -} - -@if model.build -pub fn (mut self ${model.classname}) build() ! { - switch(self.name) - build_()! -} -@end - -pub fn (mut self ${model.classname}) destroy() ! { - switch(self.name) -@if model.startupmanager - self.stop() or {} -@end - destroy_()! -} - -@end - -@end - - diff --git a/lib/core/generator/installer_client_OLD/templates/objname_model.vtemplate b/lib/core/generator/installer_client_OLD/templates/objname_model.vtemplate deleted file mode 100644 index 623599b3..00000000 --- a/lib/core/generator/installer_client_OLD/templates/objname_model.vtemplate +++ /dev/null @@ -1,155 +0,0 @@ -module ${model.name} -import freeflowuniverse.herolib.data.paramsparser -import os - -pub const version = '0.0.0' -const singleton = ${model.singleton} -const default = ${model.default} - -@if model.hasconfig -//TODO: THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE TO STRUCT BELOW, IS STRUCTURED AS HEROSCRIPT -pub fn heroscript_default() !string { -@if model.cat == .installer - heroscript:=" - !!${model.name}.configure - name:'${model.name}' - homedir: '{HOME}/hero/var/${model.name}' - configpath: '{HOME}/.config/${model.name}/admin.yaml' - username: 'admin' - password: 'secretpassword' - secret: '' - title: 'My Hero DAG' - host: 'localhost' - port: 8888 - - " -@else - heroscript:=" - !!${model.name}.configure - name:'${model.name}' - mail_from: 'info@@example.com' - mail_password: 'secretpassword' - mail_port: 587 - mail_server: 'smtp-relay.brevo.com' - mail_username: 'kristof@@incubaid.com' - - " - -// mail_from := os.getenv_opt('MAIL_FROM') or {'info@@example.com'} -// mail_password := os.getenv_opt('MAIL_PASSWORD') or {'secretpassword'} -// mail_port := (os.getenv_opt('MAIL_PORT') or {"587"}).int() -// mail_server := os.getenv_opt('MAIL_SERVER') or {'smtp-relay.brevo.com'} -// mail_username := os.getenv_opt('MAIL_USERNAME') or {'kristof@@incubaid.com'} -// -// heroscript:=" -// !!mailclient.configure name:'default' -// mail_from: '??{mail_from}' -// mail_password: '??{mail_password}' -// mail_port: ??{mail_port} -// mail_server: '??{mail_server}' -// mail_username: '??{mail_username}' -// -// " -// - -@end - - return heroscript - -} -@end - -//THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED -@if model.cat == .installer -^^[heap] -pub struct ${model.classname} { -pub mut: - name string = 'default' -@if model.hasconfig - homedir string - configpath string - username string - password string @@[secret] - secret string @@[secret] - title string - host string - port int -@end -} -@if model.hasconfig -fn cfg_play(p paramsparser.Params) !${model.classname} { - //THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above - mut mycfg := ${model.classname}{ - name: p.get_default('name', 'default')! - homedir: p.get_default('homedir', '{HOME}/hero/var/${model.name}')! - configpath: p.get_default('configpath', '{HOME}/hero/var/${model.name}/admin.yaml')! - username: p.get_default('username', 'admin')! - password: p.get_default('password', '')! - secret: p.get_default('secret', '')! - title: p.get_default('title', 'HERO DAG')! - host: p.get_default('host', 'localhost')! - port: p.get_int_default('port', 8888)! - } - - if mycfg.password == '' && mycfg.secret == '' { - return error('password or secret needs to be filled in for ${model.name}') - } - return mycfg -} -@end - -@else - -^^[heap] -pub struct ${model.classname} { -pub mut: - name string = 'default' - mail_from string - mail_password string @@[secret] - mail_port int - mail_server string - mail_username string -} - -@if model.hasconfig -fn cfg_play(p paramsparser.Params) !${model.classname} { - //THIS IS EXAMPLE CODE AND NEEDS TO BE CHANGED IN LINE WITH struct above - mut mycfg := ${model.classname}{ - name: p.get_default('name', 'default')! - mail_from: p.get('mail_from')! - mail_password: p.get('mail_password')! - mail_port: p.get_int_default('mail_port', 8888)! - mail_server: p.get('mail_server')! - mail_username: p.get('mail_username')! - } - set(mycfg)! - return mycfg -} -@end - -@end - -fn obj_init(obj_ ${model.classname})!${model.classname}{ - //never call get here, only thing we can do here is work on object itself - mut obj:=obj_ - return obj -} - -@if model.cat == .installer -//called before start if done -fn configure() ! { - @if model.cat == .installer - //mut installer := get()! - @else - //mut client := get()! - @end -@if model.templates - // mut mycode := ??tmpl('templates/atemplate.yaml') - // mut path := pathlib.get_file(path: cfg.configpath, create: true)! - // path.write(mycode)! - // console.print_debug(mycode) -@end -} -@end - - diff --git a/lib/core/generator/installer_client_OLD/templates/readme.md b/lib/core/generator/installer_client_OLD/templates/readme.md deleted file mode 100644 index f11f1905..00000000 --- a/lib/core/generator/installer_client_OLD/templates/readme.md +++ /dev/null @@ -1,63 +0,0 @@ -# ${model.name} - -${model.title} - -To get started - -```vlang - -@if model.cat == .installer - -import freeflowuniverse.herolib.installers.something.${model.name} as ${model.name}_installer - -heroscript:=" -!!${model.name}.configure name:'test' - password: '1234' - port: 7701 - -!!${model.name}.start name:'test' reset:1 -" - -${model.name}_installer.play(heroscript=heroscript)! - -//or we can call the default and do a start with reset -//mut installer:= ${model.name}_installer.get()! -//installer.start(reset:true)! - -@else - -import freeflowuniverse.herolib.clients. ${model.name} - -mut client:= ${model.name}.get()! - -client... - -@end - - - -``` - -## example heroscript - -@if model.cat == .installer -```hero -!!${model.name}.configure - homedir: '/home/user/${model.name}' - username: 'admin' - password: 'secretpassword' - title: 'Some Title' - host: 'localhost' - port: 8888 - -``` -@else -```hero -!!${model.name}.configure - secret: '...' - host: 'localhost' - port: 8888 -``` -@end - - diff --git a/lib/core/herocmds/docusaurus.v b/lib/core/herocmds/docusaurus.v index d5f6a17f..f14ed8c9 100644 --- a/lib/core/herocmds/docusaurus.v +++ b/lib/core/herocmds/docusaurus.v @@ -143,8 +143,7 @@ fn cmd_docusaurus_execute(cmd Command) ! { )! // TODO: We need to load the sitename instead, or maybe remove it - mut dsite := docusaurus.dsite_get("")! - + mut dsite := docusaurus.dsite_get('')! if buildpublish { // Build and publish production-ready artifacts diff --git a/lib/core/herocmds/git.v b/lib/core/herocmds/git.v index 28540533..d5b47b74 100644 --- a/lib/core/herocmds/git.v +++ b/lib/core/herocmds/git.v @@ -8,7 +8,7 @@ import os pub fn cmd_git(mut cmdroot Command) { mut cmd_run := Command{ name: 'git' - description: 'Work with your repos, list, commit, pull, reload, ...' + description: 'Work with your repos, list, commit, pull, reload, ...\narg is url or path, or nothing if for all repos. \nCheck -f for filter. ' // required_args: 1 usage: 'sub commands of git are ' execute: cmd_git_execute @@ -61,7 +61,7 @@ pub fn cmd_git(mut cmdroot Command) { sort_flags: true name: 'list' execute: cmd_git_execute - description: 'list all repos.' + description: 'list all repos.\nThe Argument is url or path, otherwise use -f filter.' } mut sourcetree_command := Command{ @@ -96,7 +96,6 @@ pub fn cmd_git(mut cmdroot Command) { mut allcmdsref := [&list_command, &clone_command, &push_command, &pull_command, &commit_command, &reload_command, &delete_command, &sourcetree_command, &editor_command] - for mut c in allcmdsref { c.add_flag(Flag{ flag: .bool @@ -111,7 +110,15 @@ pub fn cmd_git(mut cmdroot Command) { required: false name: 'load' abbrev: 'l' - description: 'reload the data in cache.' + description: 'reload the data in cache for selected repos.' + }) + + c.add_flag(Flag{ + flag: .string + required: false + name: 'filter' + abbrev: 'f' + description: 'filter the repos by name or path.' }) } @@ -129,13 +136,6 @@ pub fn cmd_git(mut cmdroot Command) { mut urlcmds := [&clone_command, &pull_command, &push_command, &editor_command, &sourcetree_command] for mut c in urlcmds { - c.add_flag(Flag{ - flag: .string - required: false - name: 'url' - abbrev: 'u' - description: 'url for clone operation.' - }) c.add_flag(Flag{ flag: .bool required: false @@ -163,16 +163,6 @@ pub fn cmd_git(mut cmdroot Command) { }) } - for mut c in allcmdsref { - c.add_flag(Flag{ - flag: .string - required: false - name: 'filter' - abbrev: 'f' - description: 'Filter is part of path of repo e.g. threefoldtech/info_' - }) - } - for mut c_ in allcmdsref { mut c := *c_ c.add_flag(Flag{ @@ -208,15 +198,21 @@ fn cmd_git_execute(cmd Command) ! { coderoot = os.environ()['CODEROOT'] } - mut gs := gittools.get()! - if coderoot.len > 0 { - // is a hack for now - gs = gittools.new(coderoot: coderoot)! - } + mut gs := gittools.new(coderoot: coderoot)! // create the filter for doing group actions, or action on 1 repo - mut filter := cmd.flags.get_string('filter') or { '' } + mut filter := '' + mut url := '' + mut path := '' + if cmd.args.len > 0 { + arg1 := cmd.args[0] + if arg1.starts_with('git') || arg1.starts_with('http') { + url = arg1 + } else { + path = arg1 + } + } if cmd.name in gittools.gitcmds.split(',') { mut pull := cmd.flags.get_bool('pull') or { false } @@ -228,7 +224,7 @@ fn cmd_git_execute(cmd Command) ! { } mypath := gs.do( - filter: filter + filter: cmd.flags.get_string('filter') or { '' } reload: reload recursive: recursive cmd: cmd.name @@ -236,7 +232,8 @@ fn cmd_git_execute(cmd Command) ! { pull: pull reset: reset msg: cmd.flags.get_string('message') or { '' } - url: cmd.flags.get_string('url') or { '' } + url: url + path: path )! if cmd.name == 'cd' { print('cd ${mypath}\n') diff --git a/lib/core/herocmds/playbook_lib.v b/lib/core/herocmds/playbook_lib.v index 4d4fe63c..90f03c2c 100644 --- a/lib/core/herocmds/playbook_lib.v +++ b/lib/core/herocmds/playbook_lib.v @@ -125,7 +125,7 @@ pub fn plbook_code_get(cmd Command) !string { pull := cmd.flags.get_bool('gitpull') or { false } // interactive := !cmd.flags.get_bool('script') or { false } - mut gs := gittools.get(coderoot: coderoot)! + mut gs := gittools.new(coderoot: coderoot)! if url.len > 0 { mut repo := gs.get_repo( pull: pull diff --git a/lib/core/herocmds/tofix.md b/lib/core/herocmds/tofix.md index 8bb58eaf..008ec5a6 100644 --- a/lib/core/herocmds/tofix.md +++ b/lib/core/herocmds/tofix.md @@ -48,8 +48,8 @@ play_docusaurus.play(mut plbook)! // <-- new line, optional | Problem | What to do | |---|---| -| **Wrong API name** – the code uses **`gittools.get(gittools.GitStructureArgGet{})`** – there is no `GitStructureArgGet` struct in the git‑tools package. The correct type is **`gittools.GitStructureArgs`** (or the default `gittools.GitStructure` argument). | Replace `GitStructureArgGet` with the correct type (`gittools.GitStructureArgs`). | -| **Missing import alias** – the file uses `gittools.get` and `gittools.new` but the import is just `import freeflowuniverse.herolib.develop.gittools`. That is fine, but for clarity rename the import to **`gittools`** (it already is) and use the same alias everywhere. | +| **Wrong API name** – the code uses **`gittools.new(gittools.GitStructureArgGet{})`** – there is no `GitStructureArgGet` struct in the git‑tools package. The correct type is **`gittools.GitStructureArgs`** (or the default `gittools.GitStructure` argument). | Replace `GitStructureArgGet` with the correct type (`gittools.GitStructureArgs`). | +| **Missing import alias** – the file uses `gittools.new` and `gittools.new` but the import is just `import freeflowuniverse.herolib.develop.gittools`. That is fine, but for clarity rename the import to **`gittools`** (it already is) and use the same alias everywhere. | | **Potential nil `gs`** – after a `git.clone` we do `gs = gittools.new(coderoot: coderoot)!`. This shadows the previous `gs` and loses the original configuration (e.g. `light`, `log`). The intent is to **re‑initialise** the `GitStructure` **only** when a `coderoot` is explicitly given. Keep the current flow but **document** the intention. | | **Unused variable `action_`** – the variable `action_` is used only for iteration. No problem. | | **Missing `gittools.GitCloneArgs`** – check that the struct is actually named `GitCloneArgs` in the git‑tools package. If not, change to the proper name. | Verify and, if needed, replace with the correct struct name (`gittools.GitCloneArgs`). | @@ -82,7 +82,7 @@ fn play_git(mut plbook PlayBook) ! { // ... (same as before) } else { // Default GitStructure (no args) - gittools.get(gittools.GitStructureArgs{})! + gittools.new(gittools.GitStructureArgs{})! } // ----------------------------------------------------------- diff --git a/lib/core/pathlib/factory.v b/lib/core/pathlib/factory.v index 7784adb0..bd2329ae 100644 --- a/lib/core/pathlib/factory.v +++ b/lib/core/pathlib/factory.v @@ -56,7 +56,10 @@ pub fn get_dir(args_ GetArgs) !Path { p2.absolute() if p2.exist == .no { if args.create { - os.mkdir_all(p2.absolute()) or { return error('cannot create path ${p2}, ${err}') } // Make sure that all the needed paths created + os.mkdir_all(p2.absolute()) or { + print_backtrace() + return error('cannot create path ${p2}, ${err}') + } // Make sure that all the needed paths created p2.check() } return p2 @@ -97,7 +100,10 @@ pub fn get_file(args_ GetArgs) !Path { mut parent_ := p2.parent()! parent_.check() if parent_.exist == .no { - os.mkdir_all(parent_.path) or { return error('cannot create path:${args.path}') } + os.mkdir_all(parent_.path) or { + print_backtrace() + return error('cannot create path:${args.path}') + } } if p2.exist == .no || args.empty { os.write_file(args.path, '') or { diff --git a/lib/core/playbook/factory.v b/lib/core/playbook/factory.v index 896c2bcd..a8560c11 100644 --- a/lib/core/playbook/factory.v +++ b/lib/core/playbook/factory.v @@ -31,7 +31,6 @@ pub mut: pub fn new(args_ PlayBookNewArgs) !PlayBook { mut args := args_ - mut c := base.context() or { return error('failed to get context: ${err}') } mut s := c.session_new()! diff --git a/lib/core/playbook/find.v b/lib/core/playbook/find.v index 7d97c372..413ab256 100644 --- a/lib/core/playbook/find.v +++ b/lib/core/playbook/find.v @@ -148,7 +148,7 @@ pub fn (mut plbook PlayBook) get(args FindArgs) !&Action { } else if res.len > 1 { $if debug { print_backtrace() - } + } return error("found more than one action: '${args.filter}'") } return res[0] or { panic('bug') } diff --git a/lib/core/playbook/playbook_add.v b/lib/core/playbook/playbook_add.v index 1201365c..67766c16 100644 --- a/lib/core/playbook/playbook_add.v +++ b/lib/core/playbook/playbook_add.v @@ -12,7 +12,6 @@ enum State { othertext } - // pub struct PlayBookNewArgs { // path string // text string @@ -37,23 +36,24 @@ pub fn (mut plbook PlayBook) add(args_ PlayBookNewArgs) ! { args.path = newpath.path } - if plbook.path=="" && args.path!="" { + if plbook.path == '' && args.path != '' { plbook.path = args.path } - if args.text.len>0 && args.replace.len>0{ - //now we need to replace any placeholders in the text + if args.text.len > 0 && args.replace.len > 0 { + // now we need to replace any placeholders in the text for key, value in args.replace { - if key.starts_with('@') || key.starts_with('$') || key.starts_with('[') || key.starts_with('{') { + if key.starts_with('@') || key.starts_with('$') || key.starts_with('[') + || key.starts_with('{') { args.text = args.text.replace(key, value) - }else{ - args.text = args.text.replace("@${key}", value) - args.text = args.text.replace("$\{${key}\}", value) - args.text = args.text.replace("\{${key}\}", value) + } else { + args.text = args.text.replace('@${key}', value) + args.text = args.text.replace('$\{${key}\}', value) + args.text = args.text.replace('\{${key}\}', value) } } } - + // walk over directory if args.path.len > 0 { // console.print_header("PLBOOK add path:'${args.path}'") diff --git a/lib/core/playbook/playbook_include.v b/lib/core/playbook/playbook_include.v index 8af61407..dcc595b5 100644 --- a/lib/core/playbook/playbook_include.v +++ b/lib/core/playbook/playbook_include.v @@ -2,7 +2,7 @@ module playbook import freeflowuniverse.herolib.develop.gittools // Added import for gittools -//REMARK: include is done in play_core +// REMARK: include is done in play_core // // Include external playbook actions (from git repo or local path) // // based on actions defined as `!!play.include`. diff --git a/lib/core/playcmds/factory.v b/lib/core/playcmds/factory.v index 7c7ceff3..90fce124 100644 --- a/lib/core/playcmds/factory.v +++ b/lib/core/playcmds/factory.v @@ -53,9 +53,8 @@ pub fn run(args_ PlayArgs) ! { giteaclient.play(mut plbook)! - if args.emptycheck{ + if args.emptycheck { // Ensure we did not leave any actions un‑processed plbook.empty_check()! } - } diff --git a/lib/core/playcmds/play_core.v b/lib/core/playcmds/play_core.v index 89d0ed1d..ea38bcb6 100644 --- a/lib/core/playcmds/play_core.v +++ b/lib/core/playcmds/play_core.v @@ -11,36 +11,40 @@ import os // ------------------------------------------------------------------- fn play_core(mut plbook PlayBook) ! { - // ---------------------------------------------------------------- - // 1. Include handling (play include / echo) - // ---------------------------------------------------------------- + if plbook.exists(filter: 'play.') == false && plbook.exists(filter: 'play.') == false && plbook.exists( + filter: 'core.' + ) == false { + return + } + + // ---------------------------------------------------------------- + // 1. Include handling (play include / echo) + // ---------------------------------------------------------------- // Track included paths to prevent infinite recursion mut included_paths := map[string]bool{} - - for mut action_ in plbook.find(filter: 'play.*')! { - if action_.name == 'include' { mut action := *action_ mut toreplace := action.params.get_default('replace', '')! mut playrunpath := action.params.get_default('path', '')! if playrunpath.len == 0 { action.name = 'pull' - playrunpath = gittools.get_repo_path( + mypath := gittools.path( path: playrunpath git_url: action.params.get_default('git_url', '')! git_reset: action.params.get_default_false('git_reset') git_pull: action.params.get_default_false('git_pull') )! + playrunpath = mypath.path } if playrunpath.len == 0 { return error("can't run a heroscript didn't find url or path.") } // console.print_debug('play run:\n${action_}') - if ! playrunpath.starts_with('/') { - playrunpath=os.abs_path("${plbook.path}/${playrunpath}") + if !playrunpath.starts_with('/') { + playrunpath = os.abs_path('${plbook.path}/${playrunpath}') } console.print_debug('play run include path:${playrunpath}') @@ -50,12 +54,11 @@ fn play_core(mut plbook PlayBook) ! { console.print_debug('Skipping already included path: ${playrunpath}') continue } - toreplacedict:=texttools.to_map(toreplace) + toreplacedict := texttools.to_map(toreplace) included_paths[playrunpath] = true - plbook.add(path: playrunpath,replace:toreplacedict)! - - action.done = true + plbook.add(path: playrunpath, replace: toreplacedict)! + action.done = true } if action_.name == 'echo' { content := action_.params.get_default('content', "didn't find content")! @@ -63,38 +66,35 @@ fn play_core(mut plbook PlayBook) ! { } } - - // ---------------------------------------------------------------- - // 2. Session environment handling - // ---------------------------------------------------------------- - // Guard – make sure a session exists - mut session := plbook.session - - // !!session.env_set / env_set_once - for mut action in plbook.find(filter: 'session.')! { + // ---------------------------------------------------------------- + // 2. Session environment handling + // ---------------------------------------------------------------- + // Guard – make sure a session exists + mut session := plbook.session - mut p := action.params - match action.name { - 'env_set' { - key := p.get('key')! - val := p.get('val') or { p.get('value')! } - session.env_set(key, val)! - } - 'env_set_once' { - key := p.get('key')! - val := p.get('val') or { p.get('value')! } - // Use the dedicated “set‑once” method - session.env_set_once(key, val)! - } - else { /* ignore unknown sub‑action */ } - } - action.done = true - } - + // !!session.env_set / env_set_once + for mut action in plbook.find(filter: 'session.')! { + mut p := action.params + match action.name { + 'env_set' { + key := p.get('key')! + val := p.get('val') or { p.get('value')! } + session.env_set(key, val)! + } + 'env_set_once' { + key := p.get('key')! + val := p.get('val') or { p.get('value')! } + // Use the dedicated “set‑once” method + session.env_set_once(key, val)! + } + else {} + } + action.done = true + } - // ---------------------------------------------------------------- - // 3. Template replacement in action parameters - // ---------------------------------------------------------------- + // ---------------------------------------------------------------- + // 3. Template replacement in action parameters + // ---------------------------------------------------------------- // Apply template replacement from session environment variables if session.env.len > 0 { // Create a map with name_fix applied to keys for template replacement @@ -142,5 +142,4 @@ fn play_core(mut plbook PlayBook) ! { session.save()! action.done = true } - } diff --git a/lib/core/playcmds/play_git.v b/lib/core/playcmds/play_git.v index 7838471f..c2d20431 100644 --- a/lib/core/playcmds/play_git.v +++ b/lib/core/playcmds/play_git.v @@ -11,11 +11,14 @@ import freeflowuniverse.herolib.ui.console // For verbose error reporting // --------------------------------------------------------------- fn play_git(mut plbook PlayBook) ! { - // ----------------------------------------------------------- - // !!git.define – configure the GitStructure - // ----------------------------------------------------------- + if plbook.exists(filter: 'git.') == false { + return + } + + mut gs := gittools.new()! + define_actions := plbook.find(filter: 'git.define')! - mut gs := if define_actions.len > 0 { + if define_actions.len > 0 { mut p := define_actions[0].params coderoot := p.get_default('coderoot', '')! light := p.get_default_true('light') @@ -25,18 +28,17 @@ fn play_git(mut plbook PlayBook) ! { ssh_key_path := p.get_default('ssh_key_path', '')! reload := p.get_default_false('reload') - gittools.new( - coderoot: coderoot - light: light - log: log - debug: debug - offline: offline - ssh_key_path: ssh_key_path - reload: reload + gs = gittools.new( + coderoot: coderoot + log: log + debug: debug + offline: offline + reload: reload )! - } else { - // Default GitStructure (no args) - gittools.get()! + + if light || ssh_key_path.len > 0 { + gs.config_set(light: light, ssh_key_path: ssh_key_path)! + } } // ----------------------------------------------------------- diff --git a/lib/core/playcmds/play_luadns.v b/lib/core/playcmds/play_luadns.v index eb574b83..b46b3c26 100644 --- a/lib/core/playcmds/play_luadns.v +++ b/lib/core/playcmds/play_luadns.v @@ -5,6 +5,10 @@ import freeflowuniverse.herolib.core.playbook { PlayBook } // import os fn play_luadns(mut plbook PlayBook) ! { + if plbook.exists(filter: 'luadns.') == false { + return + } + // Variables below are not used, commenting them out // mut buildroot := '${os.home_dir()}/hero/var/mdbuild' // mut publishroot := '${os.home_dir()}/hero/www/info' diff --git a/lib/core/playcmds/play_ssh.v b/lib/core/playcmds/play_ssh.v index ab0c4f0a..2e654541 100644 --- a/lib/core/playcmds/play_ssh.v +++ b/lib/core/playcmds/play_ssh.v @@ -4,6 +4,10 @@ import freeflowuniverse.herolib.osal.sshagent import freeflowuniverse.herolib.core.playbook { PlayBook } fn play_ssh(mut plbook PlayBook) ! { + if plbook.exists(filter: 'sshagent.') == false { + return + } + mut agent := sshagent.new()! for mut action in plbook.find(filter: 'sshagent.*')! { mut p := action.params diff --git a/lib/core/texttools/array.v b/lib/core/texttools/array.v index 4912bef8..3b2562c1 100644 --- a/lib/core/texttools/array.v +++ b/lib/core/texttools/array.v @@ -23,7 +23,7 @@ pub fn to_array_int(r string) []int { return r2 } -//convert a:b ,c:d,e:f to dict with keys a,c,e and corresponding values b,d,f +// convert a:b ,c:d,e:f to dict with keys a,c,e and corresponding values b,d,f pub fn to_map(mapstring string) map[string]string { mut result := map[string]string{} mut mapstring_array := to_array(mapstring) @@ -31,7 +31,7 @@ pub fn to_map(mapstring string) map[string]string { if item.contains(':') { parts := item.split(':') if parts.len == 2 { - result[parts[0].trim_space()] = parts[1].trim_space().trim("'\"").trim_space() + result[parts[0].trim_space()] = parts[1].trim_space().trim('\'"').trim_space() } else { panic('to_map: expected key:value pairs, got: ${item}') } @@ -40,7 +40,6 @@ pub fn to_map(mapstring string) map[string]string { } } return result - } // intelligent way how to map a line to a map diff --git a/lib/crypt/secp256k1/README.md b/lib/crypt/secp256k1/README.md deleted file mode 100644 index a9e44fab..00000000 --- a/lib/crypt/secp256k1/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# libsecp256k1 - -This is a lib256k1 binding for vlang. - -## Requirements - -make sure the lib is installed - -### macOS - -```bash -brew install secp256k1 -``` - -### Ubuntu - -Compile latest release, version included in Ubuntu is outdated. - -``` -apt-get install -y build-essential wget autoconf libtool - -wget https://github.com/bitcoin-core/secp256k1/archive/refs/tags/v0.3.2.tar.gz -tar -xvf v0.3.2.tar.gz - -cd secp256k1-0.3.2/ -./autogen.sh -./configure -make -j 5 -make install -``` - -### Arch - -```bash -pacman -Su extra/libsecp256k1 -``` - -### Gentoo - -```bash -emerge dev-libs/libsecp256k1 -``` - -## Features - -- [x] Generate EC keys -- [x] Load existing EC keys -- [x] Serialize keys -- [x] Derivate shared key -- [x] Sign using ECDSA -- [x] Verify ECDSA signature -- [x] Sign using Schnorr -- [x] Verify a Schnorr signature -- [ ] Support multi-signature with Schnorr - -## How to use - -There are 4 differents things / features to understand in this secp256k1 implementation (wrapper). - -### Public and Privaye keys for secp256k1 - -This is a simple private/public key schema. This wrapper deals with hexdump of keys. - -- Private key is `32 bytes` long (eg: `0x4a21f247ff3744e211e95ec478d5aba94a1d6d8bed613e8a9faece6d048399fc`) -- Public key is `33 bytes` long (eg: `0x02df72fc4fa607ca3478446750bf9f8510242c4fa5849e77373d71104cd0c82ea0`) - -In this library, you can instanciate a secp256k1 object from 3 ways: -```vlang -import freeflowuniverse.herolib.crypt.secp256k1 -secp256k1.new() -``` -Constructor without any arguments, will generate a new private and public key - -```vlang -secp256k1.new(privkey: '0x4a21f247ff3744e211e95ec478d5aba94a1d6d8bed613e8a9faece6d048399fc') -``` -Using `privkey` argument, this will create an object from private key and generate corresponding public key - -```vlang -secp256k1.new(pubkey: '0x02df72fc4fa607ca3478446750bf9f8510242c4fa5849e77373d71104cd0c82ea0') -``` -Using `privkey` argument, this will create an object with only the public key, -which can be used for shared key or signature verification - -### Shared Keys - -Library `secp256k1` have one feature which allows you to derivate a `shared intermediate common key` from -the private key of one party and the public key from the other party. - -Example: -- Shared key from `Bob Private Key` + `Alice Public Key` = `Shared Key` -- Shared key from `Alice Private Key` + `Bob Public Key` = `Shared Key` (the same) - -Using this feature, with your private key and target public key, you can derivate a `shared (secret) key` -that only you both knows. This is really interresting to switch to a symetric encryption using that key -as encryption key or use any well known secret without exchanging it. - -To use the shared key feature, just call the `sharedkeys()` method: -```vlang -bob := secp256k1.new(privhex: '0x478b45390befc3097e3e6e1a74d78a34a113f4b9ab17deb87e9b48f43893af83')! -alicepub := secp256k1.new(pubkey: '0x034a87ad6fbf83d89a91c257d4cc038828c6ed9104738ffd4bb7e5069858d4767b')! - -shared := bob.sharedkeys(alicepub) -// shared = 0xf114df29d930f0cd37f62cbca36c46773a42bf87e12edcb35d47c4bfbd20514d -``` - -This works the same in the opposite direction: -```vlang -alice := secp256k1.new(privhex: '0x8225825815f42e1c24a2e98714d99fee1a20b5ac864fbcb7a103cd0f37f0ffec')! -bobpub := secp256k1.new(pubkey: '0x03310ec949bd4f7fc24f823add1394c78e1e9d70949ccacf094c027faa20d99e21')! - -shared := alice.sharedkeys(bobpub) -// shared = 0xf114df29d930f0cd37f62cbca36c46773a42bf87e12edcb35d47c4bfbd20514d (same shared key) -``` - -### ECDSA Signature - -This is the default signature method. When doing a signature, you don't sign the actual data but you -have to sign a hash (sha256) of the data. This payload needs to be fixed length. The return signature -is a `64 bytes` long response. - -When doing a signature using ecdsa method, you sign using the private key and verify using the public key -of the same party. If **Bob** sign something, you have to verify using **Bob** public key is the signature matches. - -If signature matches, that mean that is really **Bob** who signed the hash. -Here, you need the signature and the message separately. - -```vlang -sstr := alice.sign_str("Hello World !") -valid := alicepub.verify_str(sstr, "Hello World !") -// valid = true -``` - -### Schnorr Signature - -This is the new prefered signature method. In theory, this method can in addition be able to sign -using multiple parties without storing signature of everyone, signature can be chained but this is not -implemented in this wrapper (lack of source documentation and understanding). - -In practice, code wide, wrapper take care to handle everything for you and this really looks like -the same way than ecdsa. - -```vlang -schnorr_sstr := alice.schnorr_sign_str("Hello World !") -valid := alicepub.schnorr_verify_str(schnorr_sstr, "Hello World !") -// valid = true -``` diff --git a/lib/crypt/secp256k1/secp256k1.v b/lib/crypt/secp256k1/secp256k1.v deleted file mode 100644 index 3e19fbdf..00000000 --- a/lib/crypt/secp256k1/secp256k1.v +++ /dev/null @@ -1,351 +0,0 @@ -@[translated] -module secp256k1 - -import encoding.hex -import crypto.sha256 -import encoding.base64 - -#include "@VMODROOT/secp256k1mod.h" - -#flag @VMODROOT/secp256k1mod.o -#flag -lsecp256k1 -#flag -DNO_SECP_MAIN -#flag darwin -I/opt/homebrew/include -#flag darwin -L/opt/homebrew/lib - -// linux: require libsecp256k1-dev -// macos: require brew install secp256k1 - -// -// struct definitions -// -struct Secp256k1_pubkey { - data [64]u8 -} - -struct Secp256k1_xonly_pubkey { - data [64]u8 -} - -struct Secp256k1_ecdsa_signature { - data [64]u8 -} - -struct Secp256k1_keypair { - data [96]u8 -} - -struct Secp256k1_t { - kntxt &C.secp256k1_context - seckey &u8 - compressed &u8 - pubkey Secp256k1_pubkey - xcompressed &u8 - xpubkey Secp256k1_xonly_pubkey - keypair Secp256k1_keypair -} - -struct Secp256k1_sign_t { - sig Secp256k1_ecdsa_signature - serialized &u8 - length usize -} - -struct Secp256k1_signature { - cctx &C.secp256k1_sign_t -} - -pub struct Secp256k1 { - cctx &Secp256k1_t -} - -// -// prototypes -// -fn C.secp256k1_new() &Secp256k1_t - -fn C.secp256k1_schnorr_verify(secp &Secp256k1_t, signature &u8, siglen usize, hash &u8, hashlen usize) int - -fn C.secp256k1_schnorr_sign_hash(secp &Secp256k1_t, hash &u8, length usize) &u8 - -fn C.secp256k1_sign_verify(secp &Secp256k1_t, signature &Secp256k1_sign_t, hash &u8, length usize) int - -fn C.secp256k1_sign_free(signature &Secp256k1_sign_t) - -fn C.secp256k1_load_signature(secp &Secp256k1_t, serialized &u8, length usize) &Secp256k1_sign_t - -fn C.secp256k1_sign_hash(secp &Secp256k1_t, hash &u8, length usize) &u8 - -fn C.secp265k1_shared_key(private &Secp256k1_t, public &Secp256k1_t) &u8 - -fn C.secp256k1_load_key(secp &Secp256k1_t, key &u8) int - -fn C.secp256k1_load_private_key(secp &Secp256k1_t, key &u8) int - -fn C.secp256k1_load_public_key(secp &Secp256k1_t, key &u8) int - -fn C.secp256k1_free(secp &Secp256k1_t) - -fn C.secp256k1_dumps(secp &Secp256k1_t) - -fn C.secp256k1_export(secp &Secp256k1_t) &u8 - -fn C.secp256k1_private_key(secp &Secp256k1_t) &u8 - -fn C.secp256k1_public_key(secp &Secp256k1_t) &u8 - -fn C.secp256k1_generate_key(secp &Secp256k1_t) int - -@[params] -pub struct Secp256NewArgs { -pub: - pubhex string // public key hex (eg 03310ec949bd4f7fc24f823add1394c78e1e9d70949ccacf094c027faa20d99e21) - privhex string // private key hex (eg 478b45390befc3097e3e6e1a74d78a34a113f4b9ab17deb87e9b48f43893af83) - pubbase64 string - privbase64 string - // key []u8 // is in binary form (not implemented) -} - -// get a Secp256k1 key, can start from an existing key in string hex format (starts with 0x) -// parameters: -// privhex: private key in hex format (full features will be available) -// pubhex: public key in hex format (reduced features available) -// -// keyhex string // e.g. 0x478b45390befc3097e3e6e1a74d78a34a113f4b9ab17deb87e9b48f43893af83 -// // keyhex is still supported for _backward_ compatibility only, please do not use anymore -// -// key []u8 // is in binary form (not implemented) -// generate bool = true // default will generate a new key . -pub fn new(args_ Secp256NewArgs) !Secp256k1 { - mut args := args_ - - secp := Secp256k1{} - secp.cctx = C.secp256k1_new() - - // if args.key.len > 0 && args.privhex.len > 0 { - // return error('cannot specify privhex and key at same time') - // } - - if args.privhex.len > 0 && args.pubhex.len > 0 { - return error('cannot specify private and public key at same time') - } - if args.privhex.len > 0 { - // same as keyhex (backward compatibility) - // load key from hex like 0x478b45390befc3097e3e6e1a74d78a34a113f4b9ab17deb87e9b48f43893af83 - // key is the private key - if !(args.privhex.starts_with('0x')) { - args.privhex = '0x${args.privhex}' - } - load := C.secp256k1_load_private_key(secp.cctx, args.privhex.str) - if load > 0 { - return error('invalid private key') - } - } else if args.pubhex.len > 0 { - // load key from hex like 0x478b45390befc3097e3e6e1a74d78a34a113f4b9ab17deb87e9b48f43893af83 - // key is the public key, this only allow signature check, shared keys, etc. - if !(args.pubhex.starts_with('0x')) { - args.pubhex = '0x${args.pubhex}' - } - load := C.secp256k1_load_public_key(secp.cctx, args.pubhex.str) - if load > 0 { - return error('invalid public key') - } - } else if args.privbase64.len > 0 { - keybin := base64.decode(args.privbase64) - keyhex := hex.encode(keybin) - keyhex2 := '0x${keyhex}' - return new(privhex: keyhex2)! - } else if args.pubbase64.len > 0 { - keybin := base64.decode(args.pubbase64) - keyhex := hex.encode(keybin) - keyhex2 := '0x${keyhex}' - return new(pubhex: keyhex2)! - } else { - C.secp256k1_generate_key(secp.cctx) - } - - // TODO: implement the binary key input - // TODO: check format in side and report properly - - // dumps keys for debugging purpose - // secp.keys() - - return secp -} - -// request keys dump from low level library -// this basically prints keys from internal objects (private, public, shared, x-only, ...) -// warning: this is for debug purpose -fn (s Secp256k1) keys() { - C.secp256k1_dumps(s.cctx) -} - -// export private key -// backward compatibility, please use private_key() and public_key() methods -pub fn (s Secp256k1) export() string { - key := C.secp256k1_export(s.cctx) - return unsafe { key.vstring() } -} - -// with a private key in pair with a public key, secp256k1 can derivate a shared -// key which is the same for both parties, this is really interresting to use for example -// that shared keys for symetric encryption key since it's private but common -// -// example: sharedkey(bobpriv + alicepub) = abcdef -// sharedkey(alicepriv + bobpub) = abcdef -// -// both parties can use their own private key with target public key to derivate the same -// shared commun key, this key is unique with that pair. -pub fn (s Secp256k1) sharedkeys(target Secp256k1) []u8 { - shr := C.secp265k1_shared_key(s.cctx, target.cctx) - return unsafe { shr.vbytes(32) } // 32 bytes shared key -} - -pub fn (s Secp256k1) sharedkeys_hex(target Secp256k1) string { - keybin := s.sharedkeys(target) - return hex.encode(keybin) -} - -pub fn (s Secp256k1) sharedkeys_base64(target Secp256k1) string { - keybin := s.sharedkeys(target) - return base64.encode(keybin) -} - -// returns private key in hex format -pub fn (s Secp256k1) private_key_hex() string { - key := C.secp256k1_private_key(s.cctx) - return unsafe { key.vstring()[2..] } -} - -pub fn (s Secp256k1) private_key_base64() string { - key := s.private_key_hex() - keybin := hex.decode(key) or { panic("can't decode hex") } - return base64.encode(keybin) -} - -// return public key in hex format -pub fn (s Secp256k1) public_key_hex() string { - key := C.secp256k1_public_key(s.cctx) - return unsafe { key.vstring()[2..] } -} - -pub fn (s Secp256k1) public_key_base64() string { - key := s.public_key_hex() - keybin := hex.decode(key) or { panic("can't decode hex") } - return base64.encode(keybin) -} - -// -// sign (ecdsa) data -// - we force user to pass data to ensure we hash the right way -// data to ensure signature is valid and safe -// -pub fn (s Secp256k1) sign_data(data []u8) []u8 { - // hash data - h256 := sha256.sum(data) - signature := C.secp256k1_sign_hash(s.cctx, h256.data, h256.len) - - return unsafe { signature.vbytes(64) } // 64 bytes signature -} - -// return a hex string of the signature -pub fn (s Secp256k1) sign_data_hex(data []u8) string { - payload := s.sign_data(data) - return hex.encode(payload) -} - -pub fn (s Secp256k1) sign_data_base64(data []u8) string { - payload := s.sign_data(data) - return base64.encode(payload) -} - -pub fn (s Secp256k1) sign_str(data string) []u8 { - return s.sign_data(data.bytes()) -} - -// return a hex string of the signature -pub fn (s Secp256k1) sign_str_hex(data string) string { - return s.sign_data_hex(data.bytes()) -} - -pub fn (s Secp256k1) sign_str_base64(data string) string { - payload := s.sign_data(data.bytes()) - return base64.encode(payload) -} - -// -// verify a signature -// -pub fn (s Secp256k1) verify_data(signature []u8, data []u8) bool { - // todo: check size signature - sig := Secp256k1_signature{} - sig.cctx = C.secp256k1_load_signature(s.cctx, signature.data, signature.len) - - // compute data hash to ensure we do it correctly - // - do not trust the user, do it ourself - - h256 := sha256.sum(data) - valid := C.secp256k1_sign_verify(s.cctx, sig.cctx, h256.data, h256.len) - if valid == 1 { - return true - } - - return false -} - -pub fn (s Secp256k1) verify_str_base64(signature string, input string) bool { - signature2 := base64.decode(signature) - return s.verify_data(signature2, input.bytes()) -} - -pub fn (s Secp256k1) verify_str_hex(signature string, input string) bool { - signature2 := hex.decode(signature) or { panic("couldn't decode 64") } - return s.verify_data(signature2, input.bytes()) -} - -// -// sign (schnorr) data -// - we force user to pass data to ensure we hash the right way -// data to ensure signature is valid and safe -// -pub fn (s Secp256k1) schnorr_sign_data(data []u8) []u8 { - // hash data - h256 := sha256.sum(data) - signature := C.secp256k1_schnorr_sign_hash(s.cctx, h256.data, h256.len) - - return unsafe { signature.vbytes(64) } // 64 bytes signature -} - -// return a hex string of the signature -pub fn (s Secp256k1) schnorr_sign_data_hex(data []u8) string { - payload := s.schnorr_sign_data(data) - return hex.encode(payload) -} - -pub fn (s Secp256k1) schnorr_sign_str(data string) []u8 { - return s.schnorr_sign_data(data.bytes()) -} - -// return a hex string of the signature -pub fn (s Secp256k1) schnorr_sign_str_hex(data string) string { - return s.schnorr_sign_data_hex(data.bytes()) -} - -// -// verify a signature -// -pub fn (s Secp256k1) schnorr_verify_data(signature []u8, data []u8) bool { - // compute data hash to ensure we do it correctly - // - do not trust the user, do it ourself - - h256 := sha256.sum(data) - valid := C.secp256k1_schnorr_verify(s.cctx, signature.data, signature.len, h256.data, - h256.len) - if valid == 1 { - return true - } - - return false -} - -pub fn (s Secp256k1) schnorr_verify_str(signature []u8, input string) bool { - return s.schnorr_verify_data(signature, input.bytes()) -} diff --git a/lib/crypt/secp256k1/secp256k1mod.c b/lib/crypt/secp256k1/secp256k1mod.c deleted file mode 100644 index c282dd85..00000000 --- a/lib/crypt/secp256k1/secp256k1mod.c +++ /dev/null @@ -1,460 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "secp256k1mod.h" - -static int fill_random(unsigned char* data, size_t size) { -#if defined(__linux__) || defined(__FreeBSD__) - ssize_t res = getrandom(data, size, 0); - if(res < 0 || (size_t) res != size) { - return 0; - } else { - return 1; - } - -#elif defined(__APPLE__) || defined(__OpenBSD__) - int res = getentropy(data, size); - if(res == 0) { - return 1; - } else { - return 0; - } -#endif - - return 0; -} - -static void dumphex(unsigned char *data, size_t size) { - size_t i; - - printf("0x"); - - for(i = 0; i < size; i++) { - printf("%02x", data[i]); - } - - printf("\n"); -} - -static char *hexifier(unsigned char *data, size_t size) { - char *target = calloc(sizeof(char), (size * 2) + 4); - char buffer[8]; - - strcpy(target, "0x"); - memset(buffer, 0, sizeof(buffer)); - - for(size_t i = 0; i < size; i++) { - sprintf(buffer, "%02x", data[i]); - strcat(target, buffer); - } - - return target; -} - -static unsigned char *hexparse(char *input) { - if(strncmp(input, "0x", 2) != 0) - return NULL; - - size_t length = strlen(input); - - unsigned char *target = calloc(sizeof(char), length); - char *pos = input + 2; - - for(size_t count = 0; count < length - 2; count++) { - sscanf(pos, "%2hhx", &target[count]); - pos += 2; - } - - return target; -} - -static void secp256k1_erase(unsigned char *target, size_t length) { -#if defined(__GNUC__) - // memory barrier to avoid memset optimization - memset(target, 0, length); - __asm__ __volatile__("" : : "r"(target) : "memory"); -#else - // if we can't, fill with random, still better than - // risking avoid memset - fill_random(target, length); -#endif -} - -static void secp256k1_erase_free(unsigned char *target, size_t length) { - secp256k1_erase(target, length); - free(target); -} - -secp256k1_t *secp256k1_new() { - secp256k1_t *secp = malloc(sizeof(secp256k1_t)); - unsigned char randomize[32]; - - secp->kntxt = secp256k1_context_create(SECP256K1_CONTEXT_NONE); - - if(!fill_random(randomize, sizeof(randomize))) { - printf("[-] failed to generate randomness\n"); - return NULL; - } - - // side-channel protection - int val = secp256k1_context_randomize(secp->kntxt, randomize); - assert(val); - - // allocate keys and initialize them empty - secp->seckey = calloc(sizeof(char), SECKEY_SIZE); - secp->compressed = calloc(sizeof(char), COMPPUB_SIZE); - secp->xcompressed = calloc(sizeof(char), XSERPUB_SIZE); - - return secp; -} - -void secp256k1_free(secp256k1_t *secp) { - secp256k1_context_destroy(secp->kntxt); - secp256k1_erase_free(secp->seckey, SECKEY_SIZE); - secp256k1_erase_free(secp->compressed, COMPPUB_SIZE); - secp256k1_erase_free(secp->xcompressed, XSERPUB_SIZE); - free(secp); -} - -static int secp256k1_populate_public_key(secp256k1_t *secp) { - int retval; - - retval = secp256k1_xonly_pubkey_from_pubkey(secp->kntxt, &secp->xpubkey, NULL, &secp->pubkey); - assert(retval); - - retval = secp256k1_xonly_pubkey_serialize(secp->kntxt, secp->xcompressed, &secp->xpubkey); - assert(retval); - - return 0; -} - -static int secp256k1_populate_key(secp256k1_t *secp) { - int retval; - - retval = secp256k1_ec_pubkey_create(secp->kntxt, &secp->pubkey, secp->seckey); - assert(retval); - - size_t len = COMPPUB_SIZE; - retval = secp256k1_ec_pubkey_serialize(secp->kntxt, secp->compressed, &len, &secp->pubkey, SECP256K1_EC_COMPRESSED); - assert(retval); - - // always compute the xonly pubkey as well, so we don't need to compute - // it later for schnorr - retval = secp256k1_keypair_create(secp->kntxt, &secp->keypair, secp->seckey); - assert(retval); - - return secp256k1_populate_public_key(secp); -} - -int secp256k1_generate_key(secp256k1_t *secp) { - while(1) { - if(!fill_random(secp->seckey, SECKEY_SIZE)) { - printf("[-] failed to generate randomness\n"); - return 1; - } - - if(secp256k1_ec_seckey_verify(secp->kntxt, secp->seckey) == 0) { - // try again - continue; - } - - return secp256k1_populate_key(secp); - } - - return 1; -} - -// backward compatibility -int secp256k1_load_key(secp256k1_t *secp, char *key) { - // only allow valid key size - if(strlen(key) != (SECKEY_SIZE * 2) + 2) - return 1; - - unsigned char *binkey = hexparse(key); - - free(secp->seckey); - secp->seckey = binkey; - - if(secp256k1_ec_seckey_verify(secp->kntxt, secp->seckey) == 0) { - // invalid key - return 1; - } - - return secp256k1_populate_key(secp); -} - -int secp256k1_load_private_key(secp256k1_t *secp, char *key) { - return secp256k1_load_key(secp, key); -} - -int secp256k1_load_public_key(secp256k1_t *secp, char *key) { - // only allow valid key size - if(strlen(key) != (COMPPUB_SIZE * 2) + 2) - return 1; - - unsigned char *binkey = hexparse(key); - - free(secp->compressed); - secp->compressed = binkey; - - if(!secp256k1_ec_pubkey_parse(secp->kntxt, &secp->pubkey, secp->compressed, COMPPUB_SIZE)) { - printf("[-] failed to load public key\n"); - return 1; - } - - return secp256k1_populate_public_key(secp);; -} - - -unsigned char *secp265k1_shared_key(secp256k1_t *private, secp256k1_t *public) { - unsigned char *shared = malloc(sizeof(unsigned char) * SHARED_SIZE); - - int val = secp256k1_ecdh(private->kntxt, shared, &public->pubkey, private->seckey, NULL, NULL); - assert(val); - - return shared; -} - -unsigned char *secp256k1_sign_hash(secp256k1_t *secp, unsigned char *hash, size_t length) { - secp256k1_sign_t signature; - int retval; - - if(length != SHA256_SIZE) { - printf("[-] warning: you should only sign sha-256 hash, size mismatch\n"); - printf("[-] warning: you get warned\n"); - } - - retval = secp256k1_ecdsa_sign(secp->kntxt, &signature.sig, hash, secp->seckey, NULL, NULL); - assert(retval); - - signature.serialized = malloc(sizeof(unsigned char) * SERSIG_SIZE); - - retval = secp256k1_ecdsa_signature_serialize_compact(secp->kntxt, signature.serialized, &signature.sig); - assert(retval); - - return signature.serialized; -} - -secp256k1_sign_t *secp256k1_load_signature(secp256k1_t *secp, unsigned char *serialized, size_t length) { - secp256k1_sign_t *signature; - - if(length != SERSIG_SIZE) { - printf("[-] serialized signature length mismatch, expected %u bytes\n", SERSIG_SIZE); - return NULL; - } - - signature = calloc(sizeof(secp256k1_sign_t), 1); - - signature->length = length; - signature->serialized = malloc(length); - memcpy(signature->serialized, serialized, length); - - if(!secp256k1_ecdsa_signature_parse_compact(secp->kntxt, &signature->sig, signature->serialized)) { - printf("[-] failed to parse the signature\n"); - // FIXME: cleanup - return NULL; - } - - return signature; -} - -void secp256k1_sign_free(secp256k1_sign_t *signature) { - secp256k1_erase_free(signature->serialized, signature->length); - free(signature); -} - -int secp256k1_sign_verify(secp256k1_t *secp, secp256k1_sign_t *signature, unsigned char *hash, size_t length) { - if(length != SHA256_SIZE) { - printf("[-] warning: you should only check sha-256 hash, size mismatch\n"); - } - - return secp256k1_ecdsa_verify(secp->kntxt, &signature->sig, hash, &secp->pubkey); -} - -unsigned char *secp256k1_schnorr_sign_hash(secp256k1_t *secp, unsigned char *hash, size_t length) { - unsigned char aux[32]; - unsigned char *signature; - int retval; - - if(length != SHA256_SIZE) { - printf("[-] warning: you should only sign sha-256 hash, size mismatch\n"); - printf("[-] warning: you get warned\n"); - } - - if(!fill_random(aux, sizeof(aux))) { - printf("[-] failed to generate randomness\n"); - return NULL; - } - - signature = malloc(sizeof(unsigned char) * SCHSIG_SIZE); - - retval = secp256k1_schnorrsig_sign32(secp->kntxt, signature, hash, &secp->keypair, aux); - assert(retval); - - return signature; -} - -int secp256k1_schnorr_verify(secp256k1_t *secp, unsigned char *signature, size_t siglen, unsigned char *hash, size_t hashlen) { - if(hashlen != SHA256_SIZE) { - printf("[-] warning: you should only check sha-256 hash, size mismatch\n"); - } - - if(siglen != SCHSIG_SIZE) { - printf("[-] invalid signature length, should be %u bytes\n", SCHSIG_SIZE); - return 2; - } - - return secp256k1_schnorrsig_verify(secp->kntxt, signature, hash, hashlen, &secp->xpubkey); -} - -void secp256k1_dumps(secp256k1_t *secp) { - printf("Private Key: "); - dumphex(secp->seckey, SECKEY_SIZE); - - printf("Public Key : "); - dumphex(secp->compressed, COMPPUB_SIZE); - - printf("X-Only Key : "); - dumphex(secp->xcompressed, XSERPUB_SIZE); -} - -// backward compatibility -char *secp256k1_export(secp256k1_t *secp) { - return hexifier(secp->seckey, SECKEY_SIZE); -} - -// return private key in hex format -char *secp256k1_private_key(secp256k1_t *secp) { - return secp256k1_export(secp); -} - -char *secp256k1_public_key(secp256k1_t *secp) { - return hexifier(secp->compressed, COMPPUB_SIZE); -} - - -#ifndef NO_SECP_MAIN -int main() { - secp256k1_t *wendy = secp256k1_new(); - secp256k1_generate_key(wendy); - - printf("Wendy:\n"); - dumphex(wendy->seckey, SECKEY_SIZE); - dumphex(wendy->compressed, COMPPUB_SIZE); - dumphex(wendy->xcompressed, XSERPUB_SIZE); - - // bob - secp256k1_t *bob = secp256k1_new(); - secp256k1_load_key(bob, "0x478b45390befc3097e3e6e1a74d78a34a113f4b9ab17deb87e9b48f43893af83"); - - printf("\n"); - printf("Bob:\n"); - dumphex(bob->seckey, SECKEY_SIZE); - dumphex(bob->compressed, COMPPUB_SIZE); - dumphex(bob->xcompressed, XSERPUB_SIZE); - - // export functions - char *priv = secp256k1_private_key(bob); - char *pubk = secp256k1_public_key(bob); - printf("Private export: %s\n", priv); - printf("Public export: %s\n", pubk); - free(priv); - - secp256k1_t *bobpub = secp256k1_new(); - int val = secp256k1_load_public_key(bobpub, "0x03310ec949bd4f7fc24f823add1394c78e1e9d70949ccacf094c027faa20d99e21"); - printf("Public key loader: %d\n", val); - secp256k1_dumps(bobpub); - - // alice - secp256k1_t *alice = secp256k1_new(); - secp256k1_load_key(alice, "0x8225825815f42e1c24a2e98714d99fee1a20b5ac864fbcb7a103cd0f37f0ffec"); - - printf("\n"); - printf("Alice:\n"); - dumphex(alice->seckey, SECKEY_SIZE); - dumphex(alice->compressed, COMPPUB_SIZE); - dumphex(alice->xcompressed, XSERPUB_SIZE); - - unsigned char *shared1 = secp265k1_shared_key(bob, alice); - unsigned char *shared2 = secp265k1_shared_key(alice, bob); - - printf("\n"); - printf("Shared Key:\n"); - dumphex(shared1, SHARED_SIZE); - dumphex(shared2, SHARED_SIZE); - - secp256k1_erase_free(shared1, SHARED_SIZE); - secp256k1_erase_free(shared2, SHARED_SIZE); - - // Hello, world! - unsigned char hash[32] = { - 0x31, 0x5F, 0x5B, 0xDB, 0x76, 0xD0, 0x78, 0xC4, - 0x3B, 0x8A, 0xC0, 0x06, 0x4E, 0x4A, 0x01, 0x64, - 0x61, 0x2B, 0x1F, 0xCE, 0x77, 0xC8, 0x69, 0x34, - 0x5B, 0xFC, 0x94, 0xC7, 0x58, 0x94, 0xED, 0xD3, - }; - - unsigned char *sign = secp256k1_sign_hash(bob, hash, sizeof(hash)); - - printf("\n"); - printf("Signature (ecdsa):\n"); - dumphex(sign, SERSIG_SIZE); - - secp256k1_sign_t *sigobj = secp256k1_load_signature(bob, sign, SERSIG_SIZE); - int valid = secp256k1_sign_verify(bob, sigobj, hash, sizeof(hash)); - - printf("\n"); - printf("Signature valid: %d\n", valid); - - secp256k1_sign_free(sigobj); - - // using bobpub - sigobj = secp256k1_load_signature(bobpub, sign, SERSIG_SIZE); - valid = secp256k1_sign_verify(bobpub, sigobj, hash, sizeof(hash)); - - printf("\n"); - printf("Signature valid (using bob public key only): %d\n", valid); - - secp256k1_erase_free(sign, SERSIG_SIZE); - secp256k1_sign_free(sigobj); - - sign = secp256k1_schnorr_sign_hash(bob, hash, sizeof(hash)); - - printf("\n"); - printf("Signature (schnorr):\n"); - dumphex(sign, SCHSIG_SIZE); - - valid = secp256k1_schnorr_verify(bob, sign, SCHSIG_SIZE, hash, sizeof(hash)); - - printf("\n"); - printf("Signature valid: %d\n", valid); - - valid = secp256k1_schnorr_verify(bobpub, sign, SCHSIG_SIZE, hash, sizeof(hash)); - - printf("\n"); - printf("Signature valid (using bob pubkey key only): %d\n", valid); - - secp256k1_erase_free(sign, SCHSIG_SIZE); - - printf("\n"); - printf("Wendy Export:\n"); - char *export = secp256k1_export(wendy); - printf(">> %s\n", export); - free(export); - - printf("\n"); - printf("Wendy Keys dump:\n"); - secp256k1_dumps(wendy); - - secp256k1_free(bob); - secp256k1_free(alice); - secp256k1_free(wendy); - - return 0; -} -#endif diff --git a/lib/crypt/secp256k1/secp256k1mod.h b/lib/crypt/secp256k1/secp256k1mod.h deleted file mode 100644 index c3016293..00000000 --- a/lib/crypt/secp256k1/secp256k1mod.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef SECP256K1_V_MOD - #define SECP256K1_V_MOD - - #include - #include - #include - #include - - typedef struct secp256k1_t { - secp256k1_context *kntxt; // library context - unsigned char *seckey; // ec private key - - unsigned char *compressed; // ec public key serialized - secp256k1_pubkey pubkey; // ec public key - - unsigned char *xcompressed; // x-only serialized key - secp256k1_xonly_pubkey xpubkey; // x-only public key - secp256k1_keypair keypair; // keypair opaque representation - // needed for schnorr - - } secp256k1_t; - - typedef struct secp256k1_sign_t { - secp256k1_ecdsa_signature sig; - unsigned char *serialized; - size_t length; - - } secp256k1_sign_t; - - #define SECKEY_SIZE 32 // secret key size - #define SHARED_SIZE 32 // ecdh shared key size - #define COMPPUB_SIZE 33 // compressed public key size - #define XSERPUB_SIZE 32 // x-only public key serialized size - #define SERSIG_SIZE 64 // serialized signature size - #define SCHSIG_SIZE 64 // internal schnorr signature size - - #define SHA256_SIZE 32 // sha-256 digest length - - secp256k1_t *secp256k1_new(); - void secp256k1_free(secp256k1_t *secp); - - int secp256k1_generate_key(secp256k1_t *secp); - unsigned char *secp265k1_shared_key(secp256k1_t *private, secp256k1_t *public); - unsigned char *secp256k1_sign_hash(secp256k1_t *secp, unsigned char *hash, size_t length); - - secp256k1_sign_t *secp256k1_load_signature(secp256k1_t *secp, unsigned char *serialized, size_t length); - int secp256k1_sign_verify(secp256k1_t *secp, secp256k1_sign_t *signature, unsigned char *hash, size_t length); - unsigned char *secp256k1_schnorr_sign_hash(secp256k1_t *secp, unsigned char *hash, size_t length); - int secp256k1_schnorr_verify(secp256k1_t *secp, unsigned char *signature, size_t siglen, unsigned char *hash, size_t hashlen); - void secp256k1_sign_free(secp256k1_sign_t *signature); - - char *secp256k1_export(secp256k1_t *secp); - char *secp256k1_private_key(secp256k1_t *secp); - char *secp256k1_public_key(secp256k1_t *secp); - void secp256k1_dumps(secp256k1_t *secp); - int secp256k1_load_key(secp256k1_t *secp, char *key); - - int secp256k1_load_private_key(secp256k1_t *secp, char *key); - int secp256k1_load_public_key(secp256k1_t *secp, char *key); -#endif - diff --git a/lib/crypt/secp256k1/secp256k_test.v b/lib/crypt/secp256k1/secp256k_test.v deleted file mode 100644 index 3c220338..00000000 --- a/lib/crypt/secp256k1/secp256k_test.v +++ /dev/null @@ -1,112 +0,0 @@ -module secp256k1 - -import encoding.hex -import crypto.sha256 -import freeflowuniverse.herolib.crypt.secp256k1 - -fn test_check() { - println('${'[+] initializing libsecp256 vlang wrapper'}') - - wendy := secp256k1.new()! - webdy_priv_key := wendy.private_key_hex() - webdy_pub_key := wendy.public_key_hex() - println('-------') - println('Wendy Private: ${webdy_priv_key}') - println('Wendy Public: ${webdy_pub_key}') - println('-------') - - // create 'bob' from a private key, full features will be available - bob := secp256k1.new( - privhex: '0x478b45390befc3097e3e6e1a74d78a34a113f4b9ab17deb87e9b48f43893af83' - )! - - // create 'alice' from a private key, full features will be available - alice := secp256k1.new( - privhex: '0x8225825815f42e1c24a2e98714d99fee1a20b5ac864fbcb7a103cd0f37f0ffec' - )! - - // create 'bobpub' from bob only public key, reduced features available (only sign check, shared keys, etc.) - bobpub := secp256k1.new( - pubhex: bob.public_key_hex() - )! - - // create 'alicepub' from alice only public key, reduced features available - alicepub := secp256k1.new( - pubhex: alice.public_key_hex() - )! - - shr1 := bob.sharedkeys(alice) - println('${shr1}') - - shr2 := alice.sharedkeys(bob) - println('${shr2}') - - // example in real world, where private key is available and only target public key - shr1pub := bob.sharedkeys(alicepub) - println('${shr1pub}') - - shr2pub := alice.sharedkeys(bobpub) - println('${shr2pub}') - - println('-----') - - mut message := 'Hello world, this is my awesome message' - message += message - message += message - message += message - message += message - - h256 := sha256.hexhash(message) - println('${h256}') - println('${h256.len}') - println('${sha256.sum(message.bytes())}') - - parsed := hex.decode(h256) or { panic(err) } - println('${parsed}') - println('${parsed.len}') - - // - // signature (ecdca) - // - signed := alice.sign_data(message.bytes()) - println('${signed}') - - signed_hex := alice.sign_data_hex(message.bytes()) - println('${signed_hex}') - println('${signed_hex.len}') - - signed_str := alice.sign_str(message) - println('${signed_str}') - println('${signed_str.len}') - - signed_str_hex := alice.sign_str_hex(message) - assert signed_str_hex == '656699dde22d8b89d91070dee4fc8dba136172fb54e6de475024c40e4f8d5111562212c8976b5a4ccd530bdb7f40c5d9bd2cdeeec1473656566fbb9c4576ed8c' - assert signed_str_hex.len == 128 - - // instanciate alice with only her public key - assert alicepub.verify_data(signed, message.bytes()) == true - assert alicepub.verify_str_hex(signed_str_hex, message) == true - assert alicepub.verify_str_hex(signed_str_hex, message + 's') == false - - // - // signature (schnorr) - // - // schnorr_signed := alice.schnorr_sign_data(message.bytes()) - // println('${schnorr_signed}') - - // schnorr_signed_hex := alice.schnorr_sign_data_hex(message.bytes()) - // println('${schnorr_signed_hex}') - - // schnorr_signed_str := alice.schnorr_sign_str(message) - // println('${schnorr_signed_str}') - - // schnorr_signed_str_hex := alice.schnorr_sign_str_hex(message) - // println('${schnorr_signed_str_hex}') - - // println('${alicepub.schnorr_verify_data(schnorr_signed, message.bytes())}') - // println('${alicepub.schnorr_verify_str(schnorr_signed_str, message)}') - - // // should fails, it's not the right signature method (ecdsa / schnorr) - // println('${alicepub.verify_data(schnorr_signed, message.bytes())}') - // println('${alicepub.verify_str(schnorr_signed_str, message)}') -} diff --git a/lib/crypt/secp256k1/v.mod b/lib/crypt/secp256k1/v.mod deleted file mode 100644 index cfc98b28..00000000 --- a/lib/crypt/secp256k1/v.mod +++ /dev/null @@ -1,8 +0,0 @@ -Module { - name: 'secp256k1' - description: 'secp256k1 in v' - version: '0.2.0' - license: 'MIT' - dependencies: [] -} - diff --git a/lib/data/dbfs/readme.md b/lib/data/dbfs/readme.md index 1f7ae9b8..954c524c 100644 --- a/lib/data/dbfs/readme.md +++ b/lib/data/dbfs/readme.md @@ -25,22 +25,10 @@ assert 'bbbb' == db.get('a')! ``` -## dbname - -DBName has functionality to efficiently store millions of names and generate a unique id for it, each name gets a unique id, and based on the id the name can be found back easily. - -Some string based data can be attached to one name so it becomes a highly efficient key value stor, can be used for e.g. having DB of pubkeys, for a nameserver, ... - ## dbfs examples -Each session has such a DB attached to it, data is stored on filesystem, -e.g. ideal for config sessions (which are done on context level) - - -```golang - -> TODO: fix, we refactored +```go import freeflowuniverse.herolib.data.dbfs diff --git a/lib/data/doctree/play.v b/lib/data/doctree/play.v index 1f498f29..a391bebe 100644 --- a/lib/data/doctree/play.v +++ b/lib/data/doctree/play.v @@ -4,6 +4,10 @@ import freeflowuniverse.herolib.core.playbook { PlayBook } // import freeflowuniverse.herolib.ui.console pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'doctree.') { + return + } + mut doctrees := map[string]&Tree{} mut collection_actions := plbook.find(filter: 'doctree.scan')! diff --git a/lib/data/doctree/scan.v b/lib/data/doctree/scan.v index 6300dc9e..9c9bbec1 100644 --- a/lib/data/doctree/scan.v +++ b/lib/data/doctree/scan.v @@ -80,7 +80,7 @@ pub fn (mut tree Tree) scan(args TreeScannerArgs) ! { pub fn (mut tree Tree) scan_concurrent(args_ TreeScannerArgs) ! { mut args := args_ if args.git_url.len > 0 { - mut gs := gittools.get(coderoot: args.git_root)! + mut gs := gittools.new(coderoot: args.git_root)! mut repo := gs.get_repo( url: args.git_url pull: args.git_pull diff --git a/lib/data/paramsparser/params_get_kwargs.v b/lib/data/paramsparser/params_get_kwargs.v index 59f79af1..31a03fa8 100644 --- a/lib/data/paramsparser/params_get_kwargs.v +++ b/lib/data/paramsparser/params_get_kwargs.v @@ -12,9 +12,9 @@ pub fn (params &Params) get(key_ string) !string { return p.value.trim(' ') } } - $if debug { - print_backtrace() - } + $if debug { + print_backtrace() + } return error('Did not find key:${key} in ${params}') } @@ -158,9 +158,9 @@ pub fn (params &Params) get_int_default(key string, defval int) !int { } pub fn (params &Params) get_default_true(key string) bool { - mut r := "" + mut r := '' if params.exists(key) { - r = params.get(key) or { panic("bug") } + r = params.get(key) or { panic('bug') } } r = texttools.name_fix_no_underscore(r) if r == '' || r == '1' || r == 'true' || r == 'y' || r == 'yes' { @@ -170,10 +170,11 @@ pub fn (params &Params) get_default_true(key string) bool { } pub fn (params &Params) get_default_false(key string) bool { - mut r := "" + mut r := '' if params.exists(key) { - r = params.get(key) or { panic("bug") } - } r = texttools.name_fix_no_underscore(r) + r = params.get(key) or { panic('bug') } + } + r = texttools.name_fix_no_underscore(r) if r == '' || r == '0' || r == 'false' || r == 'n' || r == 'no' { return false } diff --git a/lib/data/radixtree/correctness_test.v b/lib/data/radixtree/correctness_test.v new file mode 100644 index 00000000..4b48edd6 --- /dev/null +++ b/lib/data/radixtree/correctness_test.v @@ -0,0 +1,271 @@ +module radixtree + +import freeflowuniverse.herolib.ui.console + +// Test for the critical bug: prefix-of-existing edge inserted after the longer key +fn test_prefix_overlap_bug() ! { + console.print_debug('Testing prefix overlap bug fix') + mut rt := new(path: '/tmp/radixtree_prefix_overlap_test', reset: true)! + + // Insert longer key first + rt.set('test', 'value1'.bytes())! + rt.set('testing', 'value2'.bytes())! + + // Now insert shorter key that is a prefix - this was the bug + rt.set('te', 'value3'.bytes())! + + // Verify all keys work regardless of child iteration order + value1 := rt.get('test')! + assert value1.bytestr() == 'value1', 'Failed to get "test"' + + value2 := rt.get('testing')! + assert value2.bytestr() == 'value2', 'Failed to get "testing"' + + value3 := rt.get('te')! + assert value3.bytestr() == 'value3', 'Failed to get "te"' + + // Test that all keys are found in list + all_keys := rt.list('')! + assert 'test' in all_keys, '"test" not found in list' + assert 'testing' in all_keys, '"testing" not found in list' + assert 'te' in all_keys, '"te" not found in list' + + console.print_debug('Prefix overlap bug test passed') +} + +// Test partial overlap where neither key is a prefix of the other +fn test_partial_overlap_split() ! { + console.print_debug('Testing partial overlap split') + mut rt := new(path: '/tmp/radixtree_partial_overlap_test', reset: true)! + + // Insert keys that share a common prefix but neither is a prefix of the other + rt.set('foobar', 'value1'.bytes())! + console.print_debug('After inserting foobar') + rt.print_tree()! + + rt.set('foobaz', 'value2'.bytes())! + console.print_debug('After inserting foobaz') + rt.print_tree()! + + // Verify both keys work + value1 := rt.get('foobar')! + assert value1.bytestr() == 'value1', 'Failed to get "foobar"' + + value2 := rt.get('foobaz')! + assert value2.bytestr() == 'value2', 'Failed to get "foobaz"' + + // Test prefix search + foo_keys := rt.list('foo')! + console.print_debug('foo_keys: ${foo_keys}') + assert foo_keys.len == 2, 'Expected 2 keys with prefix "foo"' + assert 'foobar' in foo_keys, '"foobar" not found with prefix "foo"' + assert 'foobaz' in foo_keys, '"foobaz" not found with prefix "foo"' + + fooba_keys := rt.list('fooba')! + assert fooba_keys.len == 2, 'Expected 2 keys with prefix "fooba"' + + console.print_debug('Partial overlap split test passed') +} + +// Test deletion with path compression +fn test_deletion_compression() ! { + console.print_debug('Testing deletion with path compression') + mut rt := new(path: '/tmp/radixtree_deletion_compression_test', reset: true)! + + // Insert keys that will create intermediate nodes + rt.set('car', 'value1'.bytes())! + rt.set('cargo', 'value2'.bytes())! + + // Verify both keys exist + value1 := rt.get('car')! + assert value1.bytestr() == 'value1', 'Failed to get "car"' + + value2 := rt.get('cargo')! + assert value2.bytestr() == 'value2', 'Failed to get "cargo"' + + // Delete the shorter key + rt.delete('car')! + + // Verify the longer key still works (tests compression) + value2_after := rt.get('cargo')! + assert value2_after.bytestr() == 'value2', 'Failed to get "cargo" after deletion' + + // Verify the deleted key is gone + if _ := rt.get('car') { + assert false, 'Expected "car" to be deleted' + } + + console.print_debug('Deletion compression test passed') +} + +// Test large fan-out to stress the system +fn test_large_fanout() ! { + console.print_debug('Testing large fan-out') + mut rt := new(path: '/tmp/radixtree_large_fanout_test', reset: true)! + + // Insert keys with single character differences to create large fan-out + for i in 0 .. 100 { + key := 'prefix${i:03d}' + rt.set(key, 'value${i}'.bytes())! + } + + // Verify all keys can be retrieved + for i in 0 .. 100 { + key := 'prefix${i:03d}' + value := rt.get(key)! + expected := 'value${i}' + assert value.bytestr() == expected, 'Failed to get key "${key}"' + } + + // Test prefix search + prefix_keys := rt.list('prefix')! + assert prefix_keys.len == 100, 'Expected 100 keys with prefix "prefix"' + + console.print_debug('Large fan-out test passed') +} + +// Test sorted output +fn test_sorted_output() ! { + console.print_debug('Testing sorted output') + mut rt := new(path: '/tmp/radixtree_sorted_test', reset: true)! + + // Insert keys in random order + keys := ['zebra', 'apple', 'banana', 'cherry', 'date'] + for key in keys { + rt.set(key, '${key}_value'.bytes())! + } + + // Get all keys and verify they are sorted + all_keys := rt.list('')! + assert all_keys.len == keys.len, 'Expected ${keys.len} keys' + + // Check if sorted (should be: apple, banana, cherry, date, zebra) + expected_order := ['apple', 'banana', 'cherry', 'date', 'zebra'] + for i, expected_key in expected_order { + assert all_keys[i] == expected_key, 'Expected key at position ${i} to be "${expected_key}", got "${all_keys[i]}"' + } + + console.print_debug('Sorted output test passed') +} + +// Test edge case: empty key +fn test_empty_key() ! { + console.print_debug('Testing empty key') + mut rt := new(path: '/tmp/radixtree_empty_key_test', reset: true)! + + // Set empty key + rt.set('', 'empty_value'.bytes())! + + // Set regular key + rt.set('regular', 'regular_value'.bytes())! + + // Verify both work + empty_value := rt.get('')! + assert empty_value.bytestr() == 'empty_value', 'Failed to get empty key' + + regular_value := rt.get('regular')! + assert regular_value.bytestr() == 'regular_value', 'Failed to get "regular"' + + // Test list with empty prefix + all_keys := rt.list('')! + assert all_keys.len == 2, 'Expected 2 keys total' + assert '' in all_keys, 'Empty key not found in list' + assert 'regular' in all_keys, '"regular" not found in list' + + console.print_debug('Empty key test passed') +} + +// Test very long keys +fn test_long_keys() ! { + console.print_debug('Testing very long keys') + mut rt := new(path: '/tmp/radixtree_long_keys_test', reset: true)! + + // Create very long keys + long_key1 := 'a'.repeat(1000) + 'key1' + long_key2 := 'a'.repeat(1000) + 'key2' + long_key3 := 'b'.repeat(500) + 'different' + + rt.set(long_key1, 'value1'.bytes())! + rt.set(long_key2, 'value2'.bytes())! + rt.set(long_key3, 'value3'.bytes())! + + // Verify retrieval + value1 := rt.get(long_key1)! + assert value1.bytestr() == 'value1', 'Failed to get long_key1' + + value2 := rt.get(long_key2)! + assert value2.bytestr() == 'value2', 'Failed to get long_key2' + + value3 := rt.get(long_key3)! + assert value3.bytestr() == 'value3', 'Failed to get long_key3' + + // Test prefix search with long prefix + long_prefix_keys := rt.list('a'.repeat(1000))! + assert long_prefix_keys.len == 2, 'Expected 2 keys with long prefix' + + console.print_debug('Long keys test passed') +} + +// Test complex overlapping scenarios +fn test_complex_overlaps() ! { + console.print_debug('Testing complex overlapping scenarios') + mut rt := new(path: '/tmp/radixtree_complex_overlaps_test', reset: true)! + + // Create a complex set of overlapping keys + keys := [ + 'a', + 'ab', + 'abc', + 'abcd', + 'abcde', + 'abcdef', + 'abd', + 'ac', + 'b', + 'ba', + 'bb', + ] + + // Insert in random order to test robustness + for i, key in keys { + rt.set(key, 'value${i}'.bytes())! + } + + // Verify all keys can be retrieved + for i, key in keys { + value := rt.get(key)! + expected := 'value${i}' + assert value.bytestr() == expected, 'Failed to get key "${key}"' + } + + // Test various prefix searches + a_keys := rt.list('a')! + assert a_keys.len == 8, 'Expected 8 keys with prefix "a"' + + ab_keys := rt.list('ab')! + assert ab_keys.len == 6, 'Expected 6 keys with prefix "ab"' + + abc_keys := rt.list('abc')! + assert abc_keys.len == 4, 'Expected 4 keys with prefix "abc"' + + b_keys := rt.list('b')! + assert b_keys.len == 3, 'Expected 3 keys with prefix "b"' + + console.print_debug('Complex overlaps test passed') +} + +// Run all correctness tests +fn test_all_correctness() ! { + console.print_debug('Running all correctness tests...') + + test_prefix_overlap_bug()! + test_partial_overlap_split()! + test_deletion_compression()! + test_large_fanout()! + test_sorted_output()! + test_empty_key()! + test_long_keys()! + test_complex_overlaps()! + + console.print_debug('All correctness tests passed!') +} diff --git a/lib/data/radixtree/debug_deletion_test.v b/lib/data/radixtree/debug_deletion_test.v new file mode 100644 index 00000000..f60b9f92 --- /dev/null +++ b/lib/data/radixtree/debug_deletion_test.v @@ -0,0 +1,31 @@ +module radixtree + +import freeflowuniverse.herolib.ui.console + +fn test_debug_deletion() ! { + console.print_debug('Debug deletion test') + mut rt := new(path: '/tmp/radixtree_debug_deletion', reset: true)! + + console.print_debug('Inserting car') + rt.set('car', 'value1'.bytes())! + rt.print_tree()! + + console.print_debug('Inserting cargo') + rt.set('cargo', 'value2'.bytes())! + rt.print_tree()! + + console.print_debug('Testing get cargo before deletion') + value_before := rt.get('cargo')! + console.print_debug('cargo value before: ${value_before.bytestr()}') + + console.print_debug('Deleting car') + rt.delete('car')! + rt.print_tree()! + + console.print_debug('Testing get cargo after deletion') + if value_after := rt.get('cargo') { + console.print_debug('cargo value after: ${value_after.bytestr()}') + } else { + console.print_debug('ERROR: cargo not found after deletion') + } +} diff --git a/lib/data/radixtree/debug_test.v b/lib/data/radixtree/debug_test.v new file mode 100644 index 00000000..bceeda47 --- /dev/null +++ b/lib/data/radixtree/debug_test.v @@ -0,0 +1,32 @@ +module radixtree + +import freeflowuniverse.herolib.ui.console + +fn test_simple_debug() ! { + console.print_debug('=== Simple Debug Test ===') + mut rt := new(path: '/tmp/radixtree_debug_test', reset: true)! + + console.print_debug('Inserting "foobar"') + rt.set('foobar', 'value1'.bytes())! + rt.print_tree()! + + console.print_debug('Getting "foobar"') + value1 := rt.get('foobar')! + console.print_debug('Got value: ${value1.bytestr()}') + + console.print_debug('Inserting "foobaz"') + rt.set('foobaz', 'value2'.bytes())! + rt.print_tree()! + + console.print_debug('Getting "foobar" again') + value1_again := rt.get('foobar')! + console.print_debug('Got value: ${value1_again.bytestr()}') + + console.print_debug('Getting "foobaz"') + value2 := rt.get('foobaz')! + console.print_debug('Got value: ${value2.bytestr()}') + + console.print_debug('Listing all keys') + all_keys := rt.list('')! + console.print_debug('All keys: ${all_keys}') +} diff --git a/lib/data/radixtree/radixtree.v b/lib/data/radixtree/radixtree.v index 08300e84..6cdb3b1c 100644 --- a/lib/data/radixtree/radixtree.v +++ b/lib/data/radixtree/radixtree.v @@ -19,6 +19,13 @@ mut: node_id u32 // Database ID of the node } +// PathInfo tracks information about nodes in the deletion path +struct PathInfo { + node_id u32 // ID of the parent node + edge_to_child string // Edge label from parent to child + child_id u32 // ID of the child node +} + // RadixTree represents a radix tree data structure @[heap] pub struct RadixTree { @@ -74,126 +81,121 @@ pub fn (mut rt RadixTree) set(key string, value []u8) ! { mut current_id := rt.root_id mut offset := 0 - // Handle empty key case - if key.len == 0 { - mut root_node := deserialize_node(rt.db.get(current_id)!)! - root_node.is_leaf = true - root_node.value = value - rt.db.set(id: current_id, data: serialize_node(root_node))! - return - } - - for offset < key.len { + for { mut node := deserialize_node(rt.db.get(current_id)!)! + rem := key[offset..] - // Find matching child - mut matched_child := -1 - for i, child in node.children { - if key[offset..].starts_with(child.key_part) { - matched_child = i - break + if rem.len == 0 { + // turn current node into leaf (value replace) + node.is_leaf = true + node.value = value + rt.db.set(id: current_id, data: serialize_node(node))! + return + } + + mut best_idx := -1 + mut best_cp := 0 + for i, ch in node.children { + cp := get_common_prefix(rem, ch.key_part).len + if cp > 0 { + best_idx = i + best_cp = cp + break // with proper invariant there can be only one candidate } } - if matched_child == -1 { - // No matching child found, create new leaf node - key_part := key[offset..] - new_node := Node{ - key_segment: key_part + if best_idx == -1 { + // no overlap at all -> add new leaf child + new_leaf := Node{ + key_segment: rem value: value children: []NodeRef{} is_leaf: true } - // console.print_debug('Debug: Creating new leaf node with key_part "${key_part}"') - new_id := rt.db.set(data: serialize_node(new_node))! - // console.print_debug('Debug: Created node ID ${new_id}') - - // Create new child reference and update parent node - // console.print_debug('Debug: Updating parent node ${current_id} to add child reference') - - // Get fresh copy of parent node - mut parent_node := deserialize_node(rt.db.get(current_id)!)! - // console.print_debug('Debug: Parent node initially has ${parent_node.children.len} children') - - // Add new child reference - parent_node.children << NodeRef{ - key_part: key_part + new_id := rt.db.set(data: serialize_node(new_leaf))! + // reload parent (avoid stale) then append child + mut parent := deserialize_node(rt.db.get(current_id)!)! + parent.children << NodeRef{ + key_part: rem node_id: new_id } - // console.print_debug('Debug: Added child reference, now has ${parent_node.children.len} children') - - // Update parent node in DB - // console.print_debug('Debug: Serializing parent node with ${parent_node.children.len} children') - parent_data := serialize_node(parent_node) - // console.print_debug('Debug: Parent data size: ${parent_data.len} bytes') - - // First verify we can deserialize the data correctly - // console.print_debug('Debug: Verifying serialization...') - if _ := deserialize_node(parent_data) { - // console.print_debug('Debug: Serialization test successful - node has ${test_node.children.len} children') - } else { - // console.print_debug('Debug: ERROR - Failed to deserialize test data') - return error('Serialization verification failed') - } - - // Set with explicit ID to update existing node - // console.print_debug('Debug: Writing to DB...') - rt.db.set(id: current_id, data: parent_data)! - - // Verify by reading back and comparing - // console.print_debug('Debug: Reading back for verification...') - verify_data := rt.db.get(current_id)! - verify_node := deserialize_node(verify_data)! - // console.print_debug('Debug: Verification - node has ${verify_node.children.len} children') - - if verify_node.children.len == 0 { - // console.print_debug('Debug: ERROR - Node update verification failed!') - // console.print_debug('Debug: Original node children: ${node.children.len}') - // console.print_debug('Debug: Parent node children: ${parent_node.children.len}') - // console.print_debug('Debug: Verified node children: ${verify_node.children.len}') - // console.print_debug('Debug: Original data size: ${parent_data.len}') - // console.print_debug('Debug: Verified data size: ${verify_data.len}') - // console.print_debug('Debug: Data equal: ${verify_data == parent_data}') - return error('Node update failed - children array is empty') - } + // keep children sorted lexicographically + sort_children(mut parent.children) + rt.db.set(id: current_id, data: serialize_node(parent))! return } - child := node.children[matched_child] - common_prefix := get_common_prefix(key[offset..], child.key_part) - - if common_prefix.len < child.key_part.len { - // Split existing node - mut child_node := deserialize_node(rt.db.get(child.node_id)!)! - - // Create new intermediate node - mut new_node := Node{ - key_segment: child.key_part[common_prefix.len..] - value: child_node.value - children: child_node.children - is_leaf: child_node.is_leaf - } - new_id := rt.db.set(data: serialize_node(new_node))! - - // Update current node - node.children[matched_child] = NodeRef{ - key_part: common_prefix - node_id: new_id - } - rt.db.set(id: current_id, data: serialize_node(node))! + // we have overlap with child + mut chref := node.children[best_idx] + child_part := chref.key_part + if best_cp == child_part.len { + // child_part is fully consumed by rem -> descend + current_id = chref.node_id + offset += best_cp + continue } - if offset + common_prefix.len == key.len { - // Update value at existing node - mut child_node := deserialize_node(rt.db.get(child.node_id)!)! - child_node.value = value - child_node.is_leaf = true - rt.db.set(id: child.node_id, data: serialize_node(child_node))! - return + // need to split the existing child + // new intermediate node with edge = common prefix + common := get_common_prefix(rem, child_part) + child_suffix := child_part[common.len..] + rem_suffix := rem[common.len..] + + mut old_child := deserialize_node(rt.db.get(chref.node_id)!)! + + // new node representing the existing child's suffix + split_child := Node{ + key_segment: child_suffix + value: old_child.value + children: old_child.children + is_leaf: old_child.is_leaf + } + split_child_id := rt.db.set(data: serialize_node(split_child))! + + // build the intermediate + mut intermediate := Node{ + key_segment: '' // not used at traversal time + value: []u8{} + children: [ + NodeRef{ + key_part: child_suffix + node_id: split_child_id + }, + ] + is_leaf: false } - offset += common_prefix.len - current_id = child.node_id + // if our new key ends exactly at the common prefix, the intermediate becomes a leaf + if rem_suffix.len == 0 { + intermediate.is_leaf = true + intermediate.value = value + } else { + // add second child for our new key's remainder + new_leaf := Node{ + key_segment: rem_suffix + value: value + children: []NodeRef{} + is_leaf: true + } + new_leaf_id := rt.db.set(data: serialize_node(new_leaf))! + intermediate.children << NodeRef{ + key_part: rem_suffix + node_id: new_leaf_id + } + // keep children sorted + sort_children(mut intermediate.children) + } + + // write intermediate, get id + interm_id := rt.db.set(data: serialize_node(intermediate))! + + // replace the matched child on parent with the intermediate (edge = common) + node.children[best_idx] = NodeRef{ + key_part: common + node_id: interm_id + } + rt.db.set(id: current_id, data: serialize_node(node))! + return } } @@ -202,42 +204,55 @@ pub fn (mut rt RadixTree) get(key string) ![]u8 { mut current_id := rt.root_id mut offset := 0 - // Handle empty key case - if key.len == 0 { - root_node := deserialize_node(rt.db.get(current_id)!)! - if root_node.is_leaf { - return root_node.value - } - return error('Key not found') - } - - for offset < key.len { + for { node := deserialize_node(rt.db.get(current_id)!)! + rem := key[offset..] - mut found := false - for child in node.children { - if key[offset..].starts_with(child.key_part) { - if offset + child.key_part.len == key.len { - child_node := deserialize_node(rt.db.get(child.node_id)!)! - if child_node.is_leaf { - return child_node.value - } - } - current_id = child.node_id - offset += child.key_part.len - found = true - break + if rem.len == 0 { + // reached end of key + if node.is_leaf { + return node.value } - } - - if !found { return error('Key not found') } + + // binary search for matching child (since children are sorted) + child_idx := rt.find_child_with_prefix(node.children, rem) + if child_idx == -1 { + return error('Key not found') + } + + child := node.children[child_idx] + common_prefix := get_common_prefix(rem, child.key_part) + + if common_prefix.len != child.key_part.len { + // partial match - key doesn't exist + return error('Key not found') + } + + current_id = child.node_id + offset += child.key_part.len } return error('Key not found') } +// Binary search helper to find child with matching prefix +fn (rt RadixTree) find_child_with_prefix(children []NodeRef, key string) int { + if children.len == 0 || key.len == 0 { + return -1 + } + + // For now, use linear search but with proper common prefix logic + // TODO: implement true binary search based on first character + for i, child in children { + if get_common_prefix(key, child.key_part).len > 0 { + return i + } + } + return -1 +} + // Updates the value at a given key prefix, preserving the prefix while replacing the remainder pub fn (mut rt RadixTree) update(prefix string, new_value []u8) ! { mut current_id := rt.root_id @@ -283,7 +298,20 @@ pub fn (mut rt RadixTree) update(prefix string, new_value []u8) ! { pub fn (mut rt RadixTree) delete(key string) ! { mut current_id := rt.root_id mut offset := 0 - mut path := []NodeRef{} + mut path := []PathInfo{} // Track node IDs and edge labels in the path + + // Handle empty key case + if key.len == 0 { + mut root_node := deserialize_node(rt.db.get(current_id)!)! + if !root_node.is_leaf { + return error('Key not found') + } + root_node.is_leaf = false + root_node.value = []u8{} + rt.db.set(id: current_id, data: serialize_node(root_node))! + rt.maybe_compress_with_path(current_id, path)! + return + } // Find the node to delete for offset < key.len { @@ -291,21 +319,23 @@ pub fn (mut rt RadixTree) delete(key string) ! { mut found := false for child in node.children { - if key[offset..].starts_with(child.key_part) { - path << child - current_id = child.node_id - offset += child.key_part.len - found = true - - // Check if we've matched the full key - if offset == key.len { - child_node := deserialize_node(rt.db.get(child.node_id)!)! - if child_node.is_leaf { - found = true - break + common_prefix := get_common_prefix(key[offset..], child.key_part) + if common_prefix.len > 0 { + if common_prefix.len == child.key_part.len { + // Full match with child edge + path << PathInfo{ + node_id: current_id + edge_to_child: child.key_part + child_id: child.node_id } + current_id = child.node_id + offset += child.key_part.len + found = true + break + } else { + // Partial match - key doesn't exist + return error('Key not found') } - break } } @@ -314,39 +344,93 @@ pub fn (mut rt RadixTree) delete(key string) ! { } } - if path.len == 0 { + // Check if the target node is actually a leaf + mut target_node := deserialize_node(rt.db.get(current_id)!)! + if !target_node.is_leaf { return error('Key not found') } - // Get the node to delete - mut last_node := deserialize_node(rt.db.get(path.last().node_id)!)! - // If the node has children, just mark it as non-leaf - if last_node.children.len > 0 { - last_node.is_leaf = false - last_node.value = []u8{} - rt.db.set(id: path.last().node_id, data: serialize_node(last_node))! + if target_node.children.len > 0 { + target_node.is_leaf = false + target_node.value = []u8{} + rt.db.set(id: current_id, data: serialize_node(target_node))! + rt.maybe_compress_with_path(current_id, path)! return } - // If node has no children, remove it from parent - if path.len > 1 { - mut parent_node := deserialize_node(rt.db.get(path[path.len - 2].node_id)!)! + // Node has no children, remove it from parent + if path.len > 0 { + parent_info := path.last() + parent_id := parent_info.node_id + mut parent_node := deserialize_node(rt.db.get(parent_id)!)! + + // Remove the child reference for i, child in parent_node.children { - if child.node_id == path.last().node_id { + if child.node_id == current_id { parent_node.children.delete(i) break } } - rt.db.set(id: path[path.len - 2].node_id, data: serialize_node(parent_node))! - // Delete the node from the database - rt.db.delete(path.last().node_id)! + rt.db.set(id: parent_id, data: serialize_node(parent_node))! + rt.db.delete(current_id)! + + // Compress the parent if needed + rt.maybe_compress_with_path(parent_id, path[..path.len - 1])! } else { - // If this is a direct child of the root, just mark it as non-leaf - last_node.is_leaf = false - last_node.value = []u8{} - rt.db.set(id: path.last().node_id, data: serialize_node(last_node))! + // This is the root node, just mark as non-leaf + target_node.is_leaf = false + target_node.value = []u8{} + rt.db.set(id: current_id, data: serialize_node(target_node))! + } +} + +// Helper function for path compression after deletion (legacy version) +fn (mut rt RadixTree) maybe_compress(node_id u32) ! { + rt.maybe_compress_with_path(node_id, []PathInfo{})! +} + +// Helper function for path compression after deletion with path information +fn (mut rt RadixTree) maybe_compress_with_path(node_id u32, path []PathInfo) ! { + mut node := deserialize_node(rt.db.get(node_id)!)! + if node.is_leaf { + return + } + if node.children.len != 1 { + return + } + + ch := node.children[0] + mut child_node := deserialize_node(rt.db.get(ch.node_id)!)! + + // merge child into node by lifting child's children and value + node.is_leaf = child_node.is_leaf + node.value = child_node.value + node.children = child_node.children.clone() + + rt.db.set(id: node_id, data: serialize_node(node))! + rt.db.delete(ch.node_id)! + + // Update the parent's edge to include the compressed path + if path.len > 0 { + // Find the parent that points to this node + for i := path.len - 1; i >= 0; i-- { + if path[i].child_id == node_id { + parent_id := path[i].node_id + mut parent_node := deserialize_node(rt.db.get(parent_id)!)! + + // Update the edge label to include the compressed segment + for j, child in parent_node.children { + if child.node_id == node_id { + parent_node.children[j].key_part += ch.key_part + rt.db.set(id: parent_id, data: serialize_node(parent_node))! + break + } + } + break + } + } } } @@ -354,57 +438,87 @@ pub fn (mut rt RadixTree) delete(key string) ! { pub fn (mut rt RadixTree) list(prefix string) ![]string { mut result := []string{} - // Handle empty prefix case - will return all keys if prefix.len == 0 { rt.collect_all_keys(rt.root_id, '', mut result)! return result } - // Start from the root and find all matching keys - rt.find_keys_with_prefix(rt.root_id, '', prefix, mut result)! - return result + node_info := rt.find_node_for_prefix_with_path(prefix) or { + // prefix not found, return empty result + return result + } + rt.collect_all_keys(node_info.node_id, node_info.path, mut result)! + + // Filter results to only include keys that actually start with the prefix + mut filtered_result := []string{} + for key in result { + if key.starts_with(prefix) { + filtered_result << key + } + } + + return filtered_result } -// Helper function to find all keys with a given prefix -fn (mut rt RadixTree) find_keys_with_prefix(node_id u32, current_path string, prefix string, mut result []string) ! { - node := deserialize_node(rt.db.get(node_id)!)! +struct NodePathInfo { + node_id u32 + path string +} - // If the current path already matches or exceeds the prefix length - if current_path.len >= prefix.len { - // Check if the current path starts with the prefix - if current_path.starts_with(prefix) { - // If this is a leaf node, add it to the results - if node.is_leaf { - result << current_path +// Find the node where a prefix ends and return both node ID and the actual path to that node +fn (mut rt RadixTree) find_node_for_prefix_with_path(prefix string) !NodePathInfo { + mut current_id := rt.root_id + mut offset := 0 + mut current_path := '' + + for offset < prefix.len { + node := deserialize_node(rt.db.get(current_id)!)! + rem := prefix[offset..] + + mut found := false + for child in node.children { + common_prefix := get_common_prefix(rem, child.key_part) + cp_len := common_prefix.len + + if cp_len == 0 { + continue } - // Collect all keys from this subtree - for child in node.children { - child_path := current_path + child.key_part - rt.find_keys_with_prefix(child.node_id, child_path, prefix, mut result)! + if cp_len == child.key_part.len { + // child edge is fully consumed by prefix + current_id = child.node_id + current_path += child.key_part + offset += cp_len + found = true + break + } else if cp_len == rem.len { + // prefix ends inside this edge; we need to collect keys from this subtree + // but only those that actually start with the full prefix + return NodePathInfo{ + node_id: current_id + path: current_path + } + } else { + // diverged -> no matches + return error('Prefix not found') } } - return - } - // Current path is shorter than the prefix, continue searching - for child in node.children { - child_path := current_path + child.key_part - - // Check if this child's path could potentially match the prefix - if prefix.starts_with(current_path) { - // The prefix starts with the current path, so we need to check if - // the child's key_part matches the next part of the prefix - prefix_remainder := prefix[current_path.len..] - - // If the prefix remainder starts with the child's key_part or vice versa - if prefix_remainder.starts_with(child.key_part) - || (child.key_part.starts_with(prefix_remainder) - && child.key_part.len >= prefix_remainder.len) { - rt.find_keys_with_prefix(child.node_id, child_path, prefix, mut result)! - } + if !found { + return error('Prefix not found') } } + + return NodePathInfo{ + node_id: current_id + path: current_path + } +} + +// Find the node where a prefix ends (or the subtree root for that prefix) +fn (mut rt RadixTree) find_node_for_prefix(prefix string) !u32 { + info := rt.find_node_for_prefix_with_path(prefix)! + return info.node_id } // Helper function to recursively collect all keys under a node @@ -448,3 +562,16 @@ fn get_common_prefix(a string, b string) string { } return a[..i] } + +// Helper function to sort children lexicographically +fn sort_children(mut children []NodeRef) { + children.sort_with_compare(fn (a &NodeRef, b &NodeRef) int { + return if a.key_part < b.key_part { + -1 + } else if a.key_part > b.key_part { + 1 + } else { + 0 + } + }) +} diff --git a/lib/data/radixtree/radixtree_debug.v b/lib/data/radixtree/radixtree_debug.v index 86062683..b277902b 100644 --- a/lib/data/radixtree/radixtree_debug.v +++ b/lib/data/radixtree/radixtree_debug.v @@ -84,8 +84,8 @@ pub fn (mut rt RadixTree) print_tree_from_node(node_id u32, indent string) ! { // Prints the entire tree structure starting from root pub fn (mut rt RadixTree) print_tree() ! { - // console.print_debug('\nRadix Tree Structure:') - // console.print_debug('===================') + console.print_debug('\nRadix Tree Structure:') + console.print_debug('===================') rt.print_tree_from_node(rt.root_id, '')! } diff --git a/lib/data/radixtree/serialize.v b/lib/data/radixtree/serialize.v index 0c093b54..9e04421c 100644 --- a/lib/data/radixtree/serialize.v +++ b/lib/data/radixtree/serialize.v @@ -2,7 +2,9 @@ module radixtree import freeflowuniverse.herolib.data.encoder -const version = u8(1) // Current binary format version +const version = u8(2) // Updated binary format version +const max_inline_value_size = 1024 // Values larger than this are stored out-of-line +const max_inline_children = 64 // Children lists larger than this are paged // Serializes a node to bytes for storage fn serialize_node(node Node) []u8 { @@ -11,22 +13,56 @@ fn serialize_node(node Node) []u8 { // Add version byte e.add_u8(version) - // Add key segment - e.add_string(node.key_segment) - - // Add value as []u8 - e.add_u16(u16(node.value.len)) - e.data << node.value - - // Add children - e.add_u16(u16(node.children.len)) - for child in node.children { - e.add_string(child.key_part) - e.add_u32(child.node_id) + // Add flags byte (bit 0: is_leaf, bit 1: has_out_of_line_value, bit 2: has_paged_children) + mut flags := u8(0) + if node.is_leaf { + flags |= 0x01 } - // Add leaf flag - e.add_u8(if node.is_leaf { u8(1) } else { u8(0) }) + // Check if value should be stored out-of-line + has_large_value := node.value.len > max_inline_value_size + if has_large_value { + flags |= 0x02 + } + + // Check if children should be paged + has_many_children := node.children.len > max_inline_children + if has_many_children { + flags |= 0x04 + } + + e.add_u8(flags) + + // Note: key_segment is redundant and not stored (saves space) + // It's only used for debugging and can be computed from traversal path + + // Add value (inline or reference) + if has_large_value { + // TODO: Store value out-of-line and store reference ID + // For now, store inline but with u32 length to support larger values + e.add_u32(u32(node.value.len)) + e.data << node.value + } else { + e.add_u16(u16(node.value.len)) + e.data << node.value + } + + // Add children (inline or paged) + if has_many_children { + // TODO: Implement child paging for large fan-out + // For now, store inline but warn about potential size issues + e.add_u16(u16(node.children.len)) + for child in node.children { + e.add_string(child.key_part) + e.add_u32(child.node_id) + } + } else { + e.add_u16(u16(node.children.len)) + for child in node.children { + e.add_string(child.key_part) + e.add_u32(child.node_id) + } + } return e.data } @@ -37,11 +73,79 @@ fn deserialize_node(data []u8) !Node { // Read and verify version version_byte := d.get_u8()! - if version_byte != version { + if version_byte == 1 { + // Handle old format for backward compatibility + return deserialize_node_v1(data) + } else if version_byte != version { return error('Invalid version byte: expected ${version}, got ${version_byte}') } - // Read key segment + // Read flags + flags := d.get_u8()! + is_leaf := (flags & 0x01) != 0 + has_out_of_line_value := (flags & 0x02) != 0 + has_paged_children := (flags & 0x04) != 0 + + // Read value + mut value := []u8{} + if has_out_of_line_value { + // TODO: Read value reference and fetch from separate storage + value_len := d.get_u32()! + value = []u8{len: int(value_len)} + for i in 0 .. int(value_len) { + value[i] = d.get_u8()! + } + } else { + value_len := d.get_u16()! + value = []u8{len: int(value_len)} + for i in 0 .. int(value_len) { + value[i] = d.get_u8()! + } + } + + // Read children + mut children := []NodeRef{} + if has_paged_children { + // TODO: Read child page references and fetch children + children_len := d.get_u16()! + children = []NodeRef{cap: int(children_len)} + for _ in 0 .. children_len { + key_part := d.get_string()! + node_id := d.get_u32()! + children << NodeRef{ + key_part: key_part + node_id: node_id + } + } + } else { + children_len := d.get_u16()! + children = []NodeRef{cap: int(children_len)} + for _ in 0 .. children_len { + key_part := d.get_string()! + node_id := d.get_u32()! + children << NodeRef{ + key_part: key_part + node_id: node_id + } + } + } + + return Node{ + key_segment: '' // Not stored in new format + value: value + children: children + is_leaf: is_leaf + } +} + +// Backward compatibility for version 1 format +fn deserialize_node_v1(data []u8) !Node { + mut d := encoder.decoder_new(data) + + // Skip version byte (already read) + d.get_u8()! + + // Read key segment (ignored in new format) key_segment := d.get_string()! // Read value as []u8 diff --git a/lib/develop/gittools/README.md b/lib/develop/gittools/README.md index d2807609..611b6578 100644 --- a/lib/develop/gittools/README.md +++ b/lib/develop/gittools/README.md @@ -236,50 +236,39 @@ repo.remove_changes()! repo.update_submodules()! ``` -## Repository Configuration +## Repository Configuration & Status -### GitRepo Structure +The `gittools` module uses an imperative model. The `GitRepo` struct holds the *current* status of a repository in a unified `GitStatus` object. To change the state, you call explicit functions like `repo.branch_switch('my-feature')`. + +### GitRepo and GitStatus Structure ```v +// GitRepo represents a single git repository. pub struct GitRepo { pub mut: - provider string // e.g., github.com - account string // Git account name - name string // Repository name - status_remote GitRepoStatusRemote // Remote repository status - status_local GitRepoStatusLocal // Local repository status - status_wanted GitRepoStatusWanted // Desired status - config GitRepoConfig // Repository configuration - deploysshkey string // SSH key for git operations -} -``` - -### Status Tracking - -```v -// Remote Status -pub struct GitRepoStatusRemote { -pub mut: - ref_default string // Default branch hash - branches map[string]string // Branch name -> commit hash - tags map[string]string // Tag name -> commit hash + provider string + account string + name string + config GitRepoConfig + status GitStatus // Unified struct holding the CURRENT repo status. } -// Local Status -pub struct GitRepoStatusLocal { +// GitStatus holds all live status information for a repository. +pub struct GitStatus { pub mut: - branches map[string]string // Branch name -> commit hash - branch string // Current branch - tag string // Current tag -} - -// Desired Status -pub struct GitRepoStatusWanted { -pub mut: - branch string - tag string - url string // Remote repository URL - readonly bool // Prevent push/commit operations + // State from local and remote (`git fetch`) + branches map[string]string // branch name -> commit hash + tags map[string]string // tag name -> commit hash + + // Current local state + branch string // The current checked-out branch + tag string // The current checked-out tag (if any) + ahead int // Commits ahead of remote + behind int // Commits behind remote + + // Overall status + has_changes bool // True if there are uncommitted local changes + error string // Error message if any status update fails } ``` diff --git a/lib/develop/gittools/architecture.md b/lib/develop/gittools/architecture.md index 1819762e..8d6ac37f 100644 --- a/lib/develop/gittools/architecture.md +++ b/lib/develop/gittools/architecture.md @@ -85,23 +85,37 @@ site:key → config JSON site:key:repos: → GitRepo JSON ``` -### 4.2 GitRepo (excerpt) +### 4.2 GitRepo & GitStatus + +The state of a repository is captured in a single, unified `GitStatus` struct. This struct represents the *current* state of the repository after a `status_update()` operation and does not contain any "desired" or "wanted" state. State changes are performed through imperative function calls (e.g., `repo.branch_switch('main')`). ```v pub struct GitRepo { - provider string // e.g. github - account string // org/user - name string // repo name - status_remote GitRepoStatusRemote - status_local GitRepoStatusLocal - status_wanted GitRepoStatusWanted - last_load int // epoch - has_changes bool + provider string + account string + name string + config GitRepoConfig + status GitStatus // Unified CURRENT status object +} + +pub struct GitStatus { +pub mut: + // Combined local & remote state from `git fetch` + branches map[string]string // branch name -> commit hash + tags map[string]string // tag name -> commit hash + + // Current local state + branch string // current checked-out branch + tag string // current checked-out tag (if any) + ahead int // commits ahead of remote + behind int // commits behind remote + + // Overall status + has_changes bool // true if uncommitted changes exist + error string // holds error messages from status updates } ``` -Status structs separate **remote**, **local** and **desired** state, enabling `need_*` predicates to remain trivial. - --- ## 5. Execution & Behavioural Notes diff --git a/lib/develop/gittools/factory.v b/lib/develop/gittools/factory.v index a551b707..f606e363 100644 --- a/lib/develop/gittools/factory.v +++ b/lib/develop/gittools/factory.v @@ -1,44 +1,13 @@ module gittools import os -import json import freeflowuniverse.herolib.core.pathlib +import freeflowuniverse.herolib.ui.console __global ( gsinstances map[string]&GitStructure ) -@[params] -pub struct GetRepoArgs { -pub mut: - path string // if used will check if path exists if yes, just return - git_url string - git_pull bool - git_reset bool - git_root string -} - -// get_repo_path implements the GitUrlResolver interface -pub fn get_repo_path(args GetRepoArgs) !string { - if args.path!=""{ - if os.exists(args.path) { - return args.path - }else{ - if args.git_url == "" { - return error("can't resolve git repo path without url or existing path, ${args.path} does not exist.") - } - } - } - - mut gs := get(coderoot:args.git_root)! - mut repo := gs.get_repo( - url: args.git_url - pull: args.git_pull - reset: args.git_reset - )! - return repo.path() -} - pub fn reset() { gsinstances = map[string]&GitStructure{} // they key is the redis_key (hash of coderoot) } @@ -46,14 +15,11 @@ pub fn reset() { @[params] pub struct GitStructureArgsNew { pub mut: - coderoot string - light bool = true // If true, clones only the last history for all branches (clone with only 1 level deep) - log bool = true // If true, logs git commands/statements - debug bool = true - ssh_key_name string // name of ssh key to be used when loading the gitstructure - ssh_key_path string - reload bool - offline bool = false + coderoot string + log bool = true // If true, logs git commands/statements + debug bool = true + reload bool + offline bool } // Retrieve or create a new GitStructure instance with the given configuration. @@ -62,38 +28,6 @@ pub fn new(args_ GitStructureArgsNew) !&GitStructure { if args.coderoot == '' { args.coderoot = '${os.home_dir()}/code' } - mut cfg := GitStructureConfig{ - coderoot: args.coderoot - light: args.light - log: args.log - debug: args.debug - ssh_key_name: args.ssh_key_name - ssh_key_path: args.ssh_key_path - offline: args.offline - } - - return get(coderoot: args.coderoot, reload: args.reload, cfg: cfg) -} - -@[params] -pub struct GitStructureArgGet { -pub mut: - coderoot string - reload bool - cfg ?GitStructureConfig -} - -// Retrieve a GitStructure instance based on the given arguments. -pub fn get(args_ GitStructureArgGet) !&GitStructure { - mut args := args_ - if args.coderoot == '' { - args.coderoot = '${os.home_dir()}/code' - } - - // make sure coderoot exists - if !os.exists(args.coderoot) { - os.mkdir_all(args.coderoot)! - } rediskey_ := cache_key(args.coderoot) @@ -102,35 +36,30 @@ pub fn get(args_ GitStructureArgGet) !&GitStructure { mut gs := gsinstances[rediskey_] or { panic('Unexpected error: key not found in gsinstances') } - gs.load(args.reload)! return gs + } else { + console.print_debug('Loading GitStructure for ${args.coderoot}') } // Create and load the GitStructure instance. mut gs := GitStructure{ key: rediskey_ coderoot: pathlib.get_dir(path: args.coderoot, create: true)! + log: args.log + debug: args.debug + offline: args.offline } - mut cfg := args.cfg or { - mut cfg_ := GitStructureConfig{ - coderoot: 'SKIP' - } - cfg_ - } - - if cfg.coderoot != 'SKIP' { - gs.config_ = cfg - gs.config_save()! - // println(gs.config()!) + if 'OFFLINE' in os.environ() { + gs.offline = true } gs.config()! // will load the config, don't remove - gs.load(false)! - - if gs.repos.keys().len == 0 || args.reload { + if args.reload { gs.load(true)! + } else { + gs.load(false)! } gsinstances[rediskey_] = &gs @@ -158,12 +87,19 @@ pub mut: // git_pull bool pub fn path(args_ GitPathGetArgs) !pathlib.Path { mut args := args_ - if args.path.trim_space() == '' && args.currentdir { - args.path = os.getwd() + + if args.path != '' { + if os.exists(args.path) { + return pathlib.get(args.path) + } else { + if args.git_url == '' { + return error("can't resolve git repo path without url or existing path, ${args.path} does not exist.") + } + } } if args.git_url.len > 0 { - mut gs := get(coderoot: args.git_root)! + mut gs := new(coderoot: args.git_root)! mut repo := gs.get_repo( url: args.git_url pull: args.git_pull diff --git a/lib/develop/gittools/gitlocation.v b/lib/develop/gittools/gitlocation.v index c1926583..e786a514 100644 --- a/lib/develop/gittools/gitlocation.v +++ b/lib/develop/gittools/gitlocation.v @@ -32,7 +32,7 @@ pub fn (mut gs GitStructure) gitlocation_from_path(path string) !GitLocation { provider := parts[0] account := parts[1] name := parts[2] - mut repo_path := if parts.len > 3 { parts[3..].join('/') } else { '' } + mut repo_path := if parts.len > 3 { parts[3..].join('/') } else { '' } // this is for relative path in repo return GitLocation{ provider: provider diff --git a/lib/develop/gittools/gitstructure.v b/lib/develop/gittools/gitstructure.v index 50412630..00051abb 100644 --- a/lib/develop/gittools/gitstructure.v +++ b/lib/develop/gittools/gitstructure.v @@ -2,22 +2,10 @@ module gittools import crypto.md5 import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.core.redisclient -import os import freeflowuniverse.herolib.ui.console +import os import json -pub struct GitStructureConfig { -pub mut: - coderoot string // just to be informative, its not used - light bool = true // If true, clones only the last history for all branches (clone with only 1 level deep) - log bool = true // If true, logs git commands/statements - debug bool = true - ssh_key_name string - ssh_key_path string - offline bool = false -} - // GitStructure holds information about repositories within a specific code root. // This structure keeps track of loaded repositories, their configurations, and their status. @[heap] @@ -28,41 +16,33 @@ pub mut: key string // Unique key representing the git structure (default is hash of $home/code). repos map[string]&GitRepo // Map of repositories coderoot pathlib.Path + log bool = true // If true, logs git commands/statements + debug bool = true + offline bool } ////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////// // Loads all repository information from the filesystem and updates from remote if necessary. -// Use the reload argument to force reloading from the disk. -// -// Args: -// - args (StatusUpdateArgs): Arguments controlling the reload behavior. (is just a reload:bool) -pub fn (mut gitstructure GitStructure) load(reload bool) ! { - mut processed_paths := []string{} +// Use the reset argument to force reloading from the disk. +pub fn (mut gitstructure GitStructure) load(reset bool) ! { + mut processed_paths := []string{} // Re-added initialization - if reload { + if reset { gitstructure.repos = map[string]&GitRepo{} } + gitstructure.load_recursive(gitstructure.coderoot.path, mut processed_paths)! - if reload { + if reset { gitstructure.cache_reset()! } - redisclient.reset()! - redisclient.checkempty() - for _, mut repo in gitstructure.repos { - repo.status_update(reload: reload) or { - // If status_update fails, the error is already captured within the repo object. - // We log it here and continue to process other repositories. - console.print_stderr('Error updating status for repo ${repo.path()}: ${err}') - // Ensure last_load is reset to 0 if there was an error, so it's re-checked next time. - repo.last_load = 0 - repo.cache_set()! // Persist the updated last_load and error state - } + repo.status_update(reset: reset)! } + gitstructure.config_save()! } // Recursively loads repositories from the provided path, updating their statuses, does not check the status @@ -74,11 +54,13 @@ fn (mut gitstructure GitStructure) load_recursive(path string, mut processed_pat path_object := pathlib.get(path) relpath := path_object.path_relative(gitstructure.coderoot.path)! - // Limit the recursion depth to avoid deep directory traversal. - if relpath.count('/') > 4 { + // Limit the recursion depth to avoid deep directory traversal, because we have a predefined structure of git repo's. + if relpath.count('/') > 2 { return } + // console.print_debug("Loading git repositories from: ${path}, relpath:${relpath}") + items := os.ls(path) or { return error('Cannot load gitstructure because directory not found: ${path}') } @@ -87,10 +69,15 @@ fn (mut gitstructure GitStructure) load_recursive(path string, mut processed_pat current_path := os.join_path(path, item) if os.is_dir(current_path) { + excluded_dirs := ['node_modules', 'vendor', 'dist', 'build', 'bin', 'obj', 'target', + 'tmp', 'temp'] + if item.starts_with('.') || item.starts_with('_') || excluded_dirs.contains(item) { + continue + } + if os.exists(os.join_path(current_path, '.git')) { // Initialize the repository from the current path. mut repo := gitstructure.repo_init_from_path_(current_path)! - // repo.status_update()! key_ := repo.cache_key() path_ := repo.path() @@ -105,9 +92,6 @@ fn (mut gitstructure GitStructure) load_recursive(path string, mut processed_pat continue } - if item.starts_with('.') || item.starts_with('_') { - continue - } // Recursively search in subdirectories. gitstructure.load_recursive(current_path, mut processed_paths)! } @@ -142,16 +126,16 @@ fn (mut gitstructure GitStructure) repo_init_from_path_(path string, params Repo // Retrieve GitLocation from the path. gl := gitstructure.gitlocation_from_path(mypath.path)! + // console.print_debug("Initializing GitRepo from path: ${mypath.path}") // Initialize and return a GitRepo struct. mut r := GitRepo{ - gs: &gitstructure - status_remote: GitRepoStatusRemote{} - status_local: GitRepoStatusLocal{} - config: GitRepoConfig{} - provider: gl.provider - account: gl.account - name: gl.name - deploysshkey: params.ssh_key_name + gs: &gitstructure + status: GitStatus{} + config: GitRepoConfig{} + provider: gl.provider + account: gl.account + name: gl.name + deploysshkey: params.ssh_key_name } return r @@ -201,40 +185,3 @@ pub fn (mut self GitStructure) cache_reset() ! { redis.del(key)! } } - -// Load config from redis -fn (mut self GitStructure) coderoot() !pathlib.Path { - mut coderoot := pathlib.get_dir(path: self.coderoot.path, create: true)! - return coderoot -} - -////// CONFIG - -// Load config from redis -pub fn (mut self GitStructure) config() !GitStructureConfig { - mut config := self.config_ or { - mut redis := redis_get() - data := redis.get('${self.cache_key()}:config')! - mut c := GitStructureConfig{} - if data.len > 0 { - c = json.decode(GitStructureConfig, data)! - } - c - } - - return config -} - -// Reset the configuration cache for Git structures. -pub fn (mut self GitStructure) config_reset() ! { - mut redis := redis_get() - redis.del('${self.cache_key()}:config')! -} - -// save to the cache -pub fn (mut self GitStructure) config_save() ! { - // Retrieve the configuration from Redis. - mut redis := redis_get() - datajson := json.encode(self.config()!) - redis.set('${self.cache_key()}:config', datajson)! -} diff --git a/lib/develop/gittools/gitstructure_config.v b/lib/develop/gittools/gitstructure_config.v new file mode 100644 index 00000000..c3e15763 --- /dev/null +++ b/lib/develop/gittools/gitstructure_config.v @@ -0,0 +1,45 @@ +module gittools + +import json + +@[params] +pub struct GitStructureConfig { +pub mut: + light bool = true // If true, clones only the last history for all branches (clone with only 1 level deep) + ssh_key_name string + ssh_key_path string +} + +// Load config from redis +pub fn (mut self GitStructure) config() !GitStructureConfig { + mut config := self.config_ or { + mut redis := redis_get() + data := redis.get('${self.cache_key()}:config')! + mut c := GitStructureConfig{} + if data.len > 0 { + c = json.decode(GitStructureConfig, data)! + } + c + } + + return config +} + +pub fn (mut self GitStructure) config_set(args GitStructureConfig) ! { + mut redis := redis_get() + redis.set('${self.cache_key()}:config', json.encode(args))! +} + +// Reset the configuration cache for Git structures. +fn (mut self GitStructure) config_reset() ! { + mut redis := redis_get() + redis.del('${self.cache_key()}:config')! +} + +// save to the cache +fn (mut self GitStructure) config_save() ! { + // Retrieve the configuration from Redis. + mut redis := redis_get() + datajson := json.encode(self.config()!) + redis.set('${self.cache_key()}:config', datajson)! +} diff --git a/lib/develop/gittools/gittools_do.v b/lib/develop/gittools/gittools_do.v index d334a454..2f801069 100644 --- a/lib/develop/gittools/gittools_do.v +++ b/lib/develop/gittools/gittools_do.v @@ -44,17 +44,44 @@ pub mut: pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string { mut args := args_ console.print_debug('git do ${args.cmd}') + // println(args) + // $dbg; - if args.path == '' && args.url == '' && args.repo == '' && args.account == '' - && args.provider == '' && args.filter == '' { - args.path = os.getwd() + if args.path.len > 0 && args.url.len > 0 { + panic('bug') + } + if args.path.len > 0 && args.filter.len > 0 { + panic('bug') + } + if args.url.len > 0 && args.filter.len > 0 { + panic('bug') } if args.path != '' { - mut curdiro := pathlib.get_dir(path: args.path, create: false)! - mut parentpath := curdiro.parent_find('.git') or { pathlib.Path{} } - if parentpath.path != '' { - r0 := gs.repo_init_from_path_(parentpath.path)! + if args.path.contains('*') { + panic('bug') + } + if args.path == '.' { + // means current dir + args.path = os.getwd() + mut curdiro := pathlib.get_dir(path: args.path, create: false)! + mut parentpath := curdiro.parent_find('.git') or { pathlib.Path{} } + args.path = curdiro.path + } + if !os.exists(args.path) { + return error('Path does not exist: ${args.path}') + } + + r0 := gs.repo_init_from_path_(args.path)! + args.repo = r0.name + args.account = r0.account + args.provider = r0.provider + } else { + if args.url.len > 0 { + if !(args.repo == '' && args.account == '' && args.provider == '' && args.filter == '') { + return error('when specify url cannot specify repo, account, profider or filter') + } + mut r0 := gs.get_repo(url: args.url)! args.repo = r0.name args.account = r0.account args.provider = r0.provider @@ -72,12 +99,8 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string { provider: args.provider )! - // reset the status for the repo - if args.reload || args.cmd == 'reload' { - for mut repo in repos { - repo.cache_last_load_clear()! - } - gs.load(true)! + for mut repo in repos { + repo.status_update(reset: args.reload || args.cmd == 'reload')! } if args.cmd == 'list' { @@ -117,18 +140,6 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string { return '' } - - // see if a url was used means we are in 1 repo - if args.url.len > 0 { - if !(args.repo == '' && args.account == '' && args.provider == '' && args.filter == '') { - return error('when specify url cannot specify repo, account, profider or filter') - } - mut r0 := gs.get_repo(url: args.url)! - args.repo = r0.name - args.account = r0.account - args.provider = r0.provider - } - if args.cmd in 'pull,push,commit,delete'.split(',') { gs.repos_print( filter: args.filter @@ -161,11 +172,9 @@ pub fn (mut gs GitStructure) do(args_ ReposActionsArgs) !string { } console.print_debug(" --- status repo ${g.name}'s\n need_commit0:${need_commit0} \n need_pull0:${need_pull0} \n need_push0:${need_push0}") - } - console.print_debug(" --- status all repo's\n need_commit0:${need_commit0} \n need_pull0:${need_pull0} \n need_push0:${need_push0}") - $dbg; + console.print_debug(" --- status all repo's\n need_commit0:${need_commit0} \n need_pull0:${need_pull0} \n need_push0:${need_push0}") mut ok := false if need_commit0 || need_pull0 || need_push0 { diff --git a/lib/develop/gittools/repos_get.v b/lib/develop/gittools/repos_get.v index e76b2979..2f6ec4fe 100644 --- a/lib/develop/gittools/repos_get.v +++ b/lib/develop/gittools/repos_get.v @@ -119,7 +119,7 @@ pub fn (mut gitstructure GitStructure) get_repo(args_ ReposGetArgs) !&GitRepo { // repos := repositories.map('- ${it.account}.${it.name}').join_lines() $if debug { print_backtrace() - } + } return error('Found more than one repository for \n${args}') } diff --git a/lib/develop/gittools/repos_print.v b/lib/develop/gittools/repos_print.v index 230bbdd4..35edcec5 100644 --- a/lib/develop/gittools/repos_print.v +++ b/lib/develop/gittools/repos_print.v @@ -7,22 +7,15 @@ fn get_repo_status(gr GitRepo) !string { mut repo := gr mut statuses := []string{} - if repo.status_local.error.len > 0 { - mut err_msg := repo.status_local.error + if repo.status.error.len > 0 { + mut err_msg := repo.status.error if err_msg.len > 40 { err_msg = err_msg[0..40] + '...' } - statuses << 'ERROR (Local): ${err_msg}' - } - if repo.status_remote.error.len > 0 { - mut err_msg := repo.status_remote.error - if err_msg.len > 40 { - err_msg = err_msg[0..40] + '...' - } - statuses << 'ERROR (Remote): ${err_msg}' + statuses << 'ERROR: ${err_msg}' } - if repo.has_changes { + if repo.status.has_changes { statuses << 'COMMIT' } @@ -41,10 +34,10 @@ fn get_repo_status(gr GitRepo) !string { fn format_repo_info(repo GitRepo) ![]string { status := get_repo_status(repo)! - tag_or_branch := if repo.status_local.tag.len > 0 { - '[[${repo.status_local.tag}]]' // Display tag if it exists + tag_or_branch := if repo.status.tag.len > 0 { + '[[${repo.status.tag}]]' // Display tag if it exists } else { - '[${repo.status_local.branch}]' // Otherwise, display branch + '[${repo.status.branch}]' // Otherwise, display branch } relative_path := repo.get_human_path()! @@ -64,7 +57,7 @@ pub fn (mut gitstructure GitStructure) repos_print(args ReposGetArgs) ! { console.clear() // console.print_lf(1) // Removed to reduce newlines - header := 'Repositories: ${gitstructure.config()!.coderoot}' + header := 'Repositories: ${gitstructure.coderoot.path}' console.print_header(header) console.print_lf(1) // Keep one newline after header diff --git a/lib/develop/gittools/repository.v b/lib/develop/gittools/repository.v index 8a0ff23b..49127f05 100644 --- a/lib/develop/gittools/repository.v +++ b/lib/develop/gittools/repository.v @@ -1,322 +1,221 @@ +// lib/develop/gittools/repository.v module gittools import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.core as osal import os -// GitRepo holds information about a single Git repository. -@[heap] -pub struct GitRepo { -pub mut: - gs &GitStructure @[skip; str: skip] // Reference to the parent GitStructure - provider string // e.g., github.com, shortened to 'github' - account string // Git account name - name string // Repository name - status_remote GitRepoStatusRemote // Remote repository status - status_local GitRepoStatusLocal // Local repository status - status_wanted GitRepoStatusWanted // what is the status we want? - config GitRepoConfig // Repository-specific configuration - last_load int // Epoch timestamp of the last load from reality - deploysshkey string // to use with git - has_changes bool -} - -// this is the status we want, we need to work towards off -pub struct GitRepoStatusWanted { -pub mut: - branch string - tag string - url string // Remote repository URL, is basically the one we want - readonly bool // if read only then we cannot push or commit, all changes will be reset when doing pull -} - -// GitRepoStatusRemote holds remote status information for a repository. -pub struct GitRepoStatusRemote { -pub mut: - ref_default string // is the default branch hash - branches map[string]string // Branch name -> commit hash - tags map[string]string // Tag name -> commit hash - error string // Error message if remote status update fails -} - -// GitRepoStatusLocal holds local status information for a repository. -pub struct GitRepoStatusLocal { -pub mut: - branches map[string]string // Branch name -> commit hash - branch string // the current branch - tag string // If the local branch is not set, the tag may be set - error string // Error message if local status update fails -} - -// GitRepoConfig holds repository-specific configuration options. -pub struct GitRepoConfig { -pub mut: - remote_check_period int = 3600 * 24 * 7 // Seconds to wait between remote checks (0 = check every time), default 7 days -} - -// just some initialization mechanism -pub fn (mut gitstructure GitStructure) repo_new_from_gitlocation(git_location GitLocation) !&GitRepo { - mut repo := GitRepo{ - provider: git_location.provider - name: git_location.name - account: git_location.account - gs: &gitstructure - status_remote: GitRepoStatusRemote{} - status_local: GitRepoStatusLocal{} - status_wanted: GitRepoStatusWanted{} - } - gitstructure.repos[repo.cache_key()] = &repo - - return &repo -} - -////////////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////////////// - -// Commit the staged changes with the provided commit message. +// commit stages all changes and commits them with the provided message. pub fn (mut repo GitRepo) commit(msg string) ! { repo.status_update()! if !repo.need_commit()! { - console.print_debug('No changes to commit.') + console.print_debug('No changes to commit for ${repo.path()}.') return } if msg == '' { - return error('Commit message is empty.') + return error('Commit message cannot be empty.') } - repo_path := repo.path() - repo.exec('git add . -A') or { return error('Cannot add to repo: ${repo_path}. Error: ${err}') } - repo.exec('git commit -m "${msg}"') or { - return error('Cannot commit repo: ${repo_path}. Error: ${err}') + repo.exec('add . -A')! + repo.exec('commit -m "${msg}"') or { + // A common case for failure is when changes are only whitespace changes and git is configured to ignore them. + console.print_debug('Could not commit in ${repo.path()}. Maybe nothing to commit? Error: ${err}') + return } - console.print_green('Changes committed successfully.') - repo.load()! + console.print_green("Committed changes in '${repo.path()}' with message: '${msg}'.") + repo.cache_last_load_clear()! } -// Push local changes to the remote repository. +// push local changes to the remote repository. pub fn (mut repo GitRepo) push() ! { repo.status_update()! - if repo.need_push_or_pull()! { - url := repo.get_repo_url_for_clone()! - console.print_header('Pushing changes to ${url}') - // We may need to push the locally created branches - repo.exec('git push --set-upstream origin ${repo.status_local.branch}')! - console.print_green('Changes pushed successfully.') - repo.load()! - } else { - console.print_header('Everything is up to date.') + if !repo.need_push()! { + console.print_header('Nothing to push for ${repo.path()}. Already up-to-date.') + return } + + url := repo.get_repo_url_for_clone()! + console.print_header('Pushing changes to ${url}') + // This will push the current branch to its upstream counterpart. + // --set-upstream is useful for new branches. + repo.exec('push --set-upstream origin ${repo.status.branch}')! + console.print_green('Changes pushed successfully.') + repo.cache_last_load_clear()! } @[params] -pub struct PullCheckoutArgs { +pub struct PullArgs { pub mut: submodules bool // if we want to pull for submodules + reset bool // if true, will reset local changes before pulling } -// Pull remote content into the repository. -pub fn (mut repo GitRepo) pull(args_ PullCheckoutArgs) ! { +// pull remote content into the repository. +pub fn (mut repo GitRepo) pull(args PullArgs) ! { repo.status_update()! - if repo.need_checkout() { - repo.checkout()! + + if args.reset { + repo.reset()! } - repo.exec('git pull') or { return error('Cannot pull repo: ${repo.path()}. Error: ${err}') } + if repo.need_commit()! { + return error('Cannot pull in ${repo.path()} due to uncommitted changes. Either commit them or use the reset:true option.') + } - if args_.submodules { + repo.exec('pull')! + + if args.submodules { repo.update_submodules()! } - repo.load()! - console.print_green('Changes pulled successfully.') -} - -// Checkout a branch in the repository. -pub fn (mut repo GitRepo) checkout() ! { - repo.status_update()! - if repo.status_wanted.readonly { - repo.reset()! - } - if repo.need_commit()! { - return error('Cannot checkout branch due to uncommitted changes in ${repo.path()}.') - } - if repo.status_wanted.tag.len > 0 { - repo.exec('git checkout tags/${repo.status_wanted.tag}')! - } - if repo.status_wanted.branch.len > 0 { - repo.exec('git checkout ${repo.status_wanted.branch}')! - } repo.cache_last_load_clear()! + console.print_green('Changes pulled successfully from ${repo.path()}.') } -// Create a new branch in the repository. +// branch_create creates a new branch. pub fn (mut repo GitRepo) branch_create(branchname string) ! { - repo.exec('git branch -c ${branchname}') or { - return error('Cannot Create branch: ${repo.path()} to ${branchname}\nError: ${err}') - } + repo.exec('branch ${branchname}')! repo.cache_last_load_clear()! - console.print_green('Branch ${branchname} created successfully.') + console.print_green('Branch ${branchname} created successfully in ${repo.path()}.') } +// branch_switch switches to a different branch. pub fn (mut repo GitRepo) branch_switch(branchname string) ! { - repo.exec('git switch ${branchname}') or { - return error('Cannot switch branch: ${repo.path()} to ${branchname}\nError: ${err}') + if repo.need_commit()! { + return error('Cannot switch branch in ${repo.path()} due to uncommitted changes.') } - console.print_green('Branch ${branchname} switched successfully.') - repo.status_local.branch = branchname - repo.status_local.tag = '' - repo.pull()! + repo.exec('switch ${branchname}')! + console.print_green('Switched to branch ${branchname} in ${repo.path()}.') + repo.status.branch = branchname + repo.status.tag = '' + repo.cache_last_load_clear()! } -// Create a new branch in the repository. +// tag_create creates a new tag. pub fn (mut repo GitRepo) tag_create(tagname string) ! { - repo_path := repo.path() - repo.exec('git tag ${tagname}') or { - return error('Cannot create tag: ${repo_path}. Error: ${err}') - } - console.print_green('Tag ${tagname} created successfully.') + repo.exec('tag ${tagname}')! + console.print_green('Tag ${tagname} created successfully in ${repo.path()}.') + repo.cache_last_load_clear()! } +// tag_switch checks out a specific tag. pub fn (mut repo GitRepo) tag_switch(tagname string) ! { - repo.exec('git checkout ${tagname}') or { - return error('Cannot switch to tag: ${tagname}. Error: ${err}') + if repo.need_commit()! { + return error('Cannot switch to tag in ${repo.path()} due to uncommitted changes.') } - console.print_green('Tag ${tagname} activated.') - repo.status_local.branch = '' - repo.status_local.tag = tagname - repo.pull()! + repo.exec('checkout tags/${tagname}')! + console.print_green('Switched to tag ${tagname} in ${repo.path()}.') + repo.status.branch = '' + repo.status.tag = tagname + repo.cache_last_load_clear()! } -// Create a new branch in the repository. +// tag_exists checks if a tag exists in the repository. pub fn (mut repo GitRepo) tag_exists(tag string) !bool { - repo.exec('git show ${tag}') or { return false } - return true + repo.status_update()! + return tag in repo.status.tags } -// Deletes the Git repository +// delete removes the repository from the filesystem and cache. pub fn (mut repo GitRepo) delete() ! { repo_path := repo.path() key := repo.cache_key() repo.cache_delete()! osal.rm(repo_path)! - repo.gs.repos.delete(key) // Remove from GitStructure's repos map + repo.gs.repos.delete(key) } -// Create GitLocation from the path within the Git repository -pub fn (mut gs GitRepo) gitlocation_from_path(path string) !GitLocation { +// gitlocation_from_path creates a GitLocation from a path inside this repository. +pub fn (mut repo GitRepo) gitlocation_from_path(path string) !GitLocation { if path.starts_with('/') || path.starts_with('~') { return error('Path must be relative, cannot start with / or ~') } + repo.status_update()! - mut git_path := gs.patho()! + mut git_path := repo.patho()! repo_path := git_path.path abs_path := os.abs_path(path) - // Check if path is inside git repo if !abs_path.starts_with(repo_path) { return error('Path ${path} is not inside the git repository at ${repo_path}') } - // Get relative path in relation to root of gitrepo - rel_path := abs_path[repo_path.len + 1..] // +1 to skip the trailing slash + rel_path := abs_path[repo_path.len + 1..] if !os.exists(abs_path) { return error('Path does not exist inside the repository: ${abs_path}') } - mut branch_or_tag := gs.status_wanted.branch - if gs.status_wanted.tag.len > 0 { - branch_or_tag = gs.status_wanted.tag + mut branch_or_tag := repo.status.branch + if repo.status.tag != '' { + branch_or_tag = repo.status.tag } return GitLocation{ - provider: gs.provider - account: gs.account - name: gs.name + provider: repo.provider + account: repo.account + name: repo.name branch_or_tag: branch_or_tag - path: rel_path // relative path in relation to git repo + path: rel_path } } -// Check if repo path exists and validate fields +// init validates the repository's configuration and path. pub fn (mut repo GitRepo) init() ! { - path_string := repo.path() - if repo.provider == '' { - return error('Provider cannot be empty') - } - if repo.account == '' { - return error('Account cannot be empty') - } - if repo.name == '' { - return error('Name cannot be empty') + if repo.provider == '' || repo.account == '' || repo.name == '' { + return error('Repo identifier (provider, account, name) cannot be empty for ${repo.path()}') } - if !os.exists(path_string) { - return error('Path does not exist: ${path_string}') - } - - // Check if deploy key is set in repo config - if repo.deploysshkey.len > 0 { - git_config := repo.exec('git config --get core.sshCommand') or { '' } - if !git_config.contains(repo.deploysshkey) { - repo.set_sshkey(repo.deploysshkey)! - } - } - - // Check that either tag or branch is set on wanted, but not both - if repo.status_wanted.tag.len > 0 && repo.status_wanted.branch.len > 0 { - return error('Cannot set both tag and branch in wanted status. Choose one or the other.') + if !os.exists(repo.path()) { + return error('Path does not exist: ${repo.path()}') } } -// Set the ssh key on the repo +// set_sshkey configures the repository to use a specific SSH key for git operations. fn (mut repo GitRepo) set_sshkey(key_name string) ! { - // will use this dir to find and set key from ssh_dir := os.join_path(os.home_dir(), '.ssh') key := osal.get_ssh_key(key_name, directory: ssh_dir) or { return error('SSH Key with name ${key_name} not found.') } - - private_key := key.private_key_path()! - repo.exec('git config core.sshcommand "ssh -i ~/.ssh/${private_key.path}"')! + private_key_path := key.private_key_path()! + repo.exec('config core.sshCommand "ssh -i ${private_key_path}"')! repo.deploysshkey = key_name } -// Removes all changes from the repo; be cautious +// remove_changes hard resets the repository to HEAD and cleans untracked files. pub fn (mut repo GitRepo) remove_changes() ! { repo.status_update()! - if repo.has_changes { - console.print_header('Removing changes in ${repo.path()}') - repo.exec('git reset HEAD --hard && git clean -xfd') or { - return error("can't remove changes on repo: ${repo.path()}.\n${err}") - // TODO: we can do this fall back later - // console.print_header('Could not remove changes; will re-clone ${repo.path()}') - // mut p := repo.patho()! - // p.delete()! // remove path, this will re-clone the full thing - // repo.load_from_url()! - } - repo.load()! + if repo.status.has_changes { + console.print_header('Removing all local changes in ${repo.path()}') + repo.exec('reset --hard HEAD && git clean -fdx')! + repo.cache_last_load_clear()! } } -// alias for remove changes +// reset is an alias for remove_changes. pub fn (mut repo GitRepo) reset() ! { - return repo.remove_changes() + repo.remove_changes()! } -// Update submodules +// update_submodules initializes and updates all submodules. fn (mut repo GitRepo) update_submodules() ! { - repo.exec('git submodule update --init --recursive') or { - return error('Cannot update submodules for repo: ${repo.path()}. Error: ${err}') - } + repo.exec('submodule update --init --recursive')! } +// exec executes a command within the repository's directory. +// This is the designated wrapper for all git commands for this repo. fn (repo GitRepo) exec(cmd_ string) !string { repo_path := repo.path() - cmd := 'cd ${repo_path} && ${cmd_}' + + // if cmd_.starts_with("pull ") || cmd_.starts_with("push ") || cmd_.starts_with("fetch "){ + // //means we need to be able to fetch from remote repo, check we have a key registered e.g. for gitea + // $dbg; + + // } + cmd := 'cd ${repo_path} && git ${cmd_}' // console.print_debug(cmd) r := os.execute(cmd) if r.exit_code != 0 { - return error('Repo failed to exec cmd: ${cmd}\n${r.output})') + return error('Repo command failed:\nCMD: ${cmd}\nOUT: ${r.output})') } - return r.output + return r.output.trim_space() } diff --git a/lib/develop/gittools/repository_cache.v b/lib/develop/gittools/repository_cache.v index 21405273..2a4044b8 100644 --- a/lib/develop/gittools/repository_cache.v +++ b/lib/develop/gittools/repository_cache.v @@ -13,6 +13,7 @@ fn (mut repo GitRepo) cache_set() ! { mut redis_client := redis_get() repo_json := json.encode(repo) cache_key := repo.cache_key() + // println("Caching repository ${repo.name} at ${cache_key}") redis_client.set(cache_key, repo_json)! } @@ -30,13 +31,21 @@ fn (mut repo GitRepo) cache_get() ! { } } +fn (mut repo GitRepo) cache_exists() !bool { + mut repo_json := '' + mut redis_client := redis_get() + cache_key := repo.cache_key() + // println("${repo.name} : Checking if cache exists at ${cache_key}") + repo_json = redis_client.get(cache_key) or { return false } + // println(repo_json) + return repo_json.len > 0 +} + // Remove cache fn (mut repo GitRepo) cache_delete() ! { mut redis_client := redis_get() cache_key := repo.cache_key() redis_client.del(cache_key) or { return error('Cannot delete the repo cache due to: ${err}') } - // TODO: report v bug, function should work without return as well - return } // put the data of last load on 0, means first time a git status check will be done it will update its info diff --git a/lib/develop/gittools/repository_clone.v b/lib/develop/gittools/repository_clone.v index a3fc907c..bea49343 100644 --- a/lib/develop/gittools/repository_clone.v +++ b/lib/develop/gittools/repository_clone.v @@ -2,6 +2,7 @@ module gittools import freeflowuniverse.herolib.ui.console import os +import freeflowuniverse.herolib.core.pathlib @[params] pub struct GitCloneArgs { @@ -23,7 +24,20 @@ pub fn (mut gitstructure GitStructure) clone(args GitCloneArgs) !&GitRepo { // gitlocatin comes just from the url, not from fs of whats already there git_location := gitstructure.gitlocation_from_url(args.url)! - mut repo := gitstructure.repo_new_from_gitlocation(git_location)! + // Initialize a new GitRepo instance + mut repo := GitRepo{ + gs: &gitstructure + provider: git_location.provider + account: git_location.account + name: git_location.name + deploysshkey: args.sshkey // Use the sshkey from args + config: GitRepoConfig{} // Initialize with default config + status: GitStatus{} // Initialize with default status + } + + // Add the new repo to the gitstructure's repos map + key_ := repo.cache_key() + gitstructure.repos[key_] = &repo mut repopath := repo.patho()! if repopath.exists() { @@ -57,12 +71,10 @@ pub fn (mut gitstructure GitStructure) clone(args GitCloneArgs) !&GitRepo { return error('Cannot clone the repository due to: \n${result.output}') } - repo.load()! - if repo.need_checkout() { - repo.checkout()! - } + // The repo is now cloned. Load its initial status. + repo.load_internal()! console.print_green("The repository '${repo.name}' cloned into ${parent_dir}.") - return repo + return &repo // Return the initialized repo } diff --git a/lib/develop/gittools/repository_info.v b/lib/develop/gittools/repository_info.v index 84c2a47b..40f1fae2 100644 --- a/lib/develop/gittools/repository_info.v +++ b/lib/develop/gittools/repository_info.v @@ -1,140 +1,37 @@ +// lib/develop/gittools/repository_info.v module gittools -// FUNCITONS TO GET INFO FROM REALITY - -// Retrieves a list of unstaged changes in the repository. -// -// This function returns a list of files that are modified or untracked. -// -// Returns: -// - An array of strings representing file paths of unstaged changes. -// - Throws an error if the command execution fails. -pub fn (repo GitRepo) get_changes_unstaged() ![]string { - unstaged_result := repo.exec('git ls-files --other --modified --exclude-standard') or { - return error('Failed to check for unstaged changes: ${repo.path()}\n${err}') - } - - // Filter out any empty lines from the result. - return unstaged_result.split('\n').filter(it.len > 0) -} - -// Retrieves a list of staged changes in the repository. -// -// This function returns a list of files that are staged and ready to be committed. -// -// Returns: -// - An array of strings representing file paths of staged changes. -// - Throws an error if the command execution fails. -pub fn (repo GitRepo) get_changes_staged() ![]string { - staged_result := repo.exec('git diff --name-only --staged') or { - return error('Failed to check for staged changes: ${repo.path()}\n${err}') - } - // Filter out any empty lines from the result. - return staged_result.split('\n').filter(it.len > 0) -} - -// Check if there are any unstaged or untracked changes in the repository. -pub fn (mut repo GitRepo) detect_changes() !bool { - r0 := repo.get_changes_unstaged()! - r1 := repo.get_changes_staged()! - if r0.len + r1.len > 0 { - return true - } - return false -} - -// Check if there are staged changes to commit. +// need_commit checks if there are staged or unstaged changes. pub fn (mut repo GitRepo) need_commit() !bool { - return repo.has_changes + repo.status_update()! + return repo.status.has_changes } -// Check if the repository has local changes that need to be pushed to remote +// need_push checks if the repository has local commits that need to be pushed. pub fn (mut repo GitRepo) need_push() !bool { repo.status_update()! - last_remote_commit := repo.get_last_remote_commit() or { - return error('Failed to get last remote commit: ${repo.path()}\n${err}') - } - last_local_commit := repo.get_last_local_commit()! - // If remote commit is empty, it means the branch doesn't exist remotely yet - if last_remote_commit.len == 0 { - return true - } - // If local commit is different from remote and exists, we need to push - return last_local_commit != last_remote_commit + return repo.status.ahead > 0 } -// Check if the repository needs to pull changes from remote +// need_pull checks if the repository needs to pull changes from the remote. pub fn (mut repo GitRepo) need_pull() !bool { repo.status_update()! - last_remote_commit := repo.get_last_remote_commit() or { - return error('Failed to get last remote commit: ${repo.path()}\n${err}') - } - // If remote doesn't exist, no need to pull - if last_remote_commit.len == 0 { - return false - } - // Check if the remote commit exists in our local history - // If it doesn't exist, we need to pull - result := repo.exec('git merge-base --is-ancestor ${last_remote_commit} HEAD') or { - // if err.msg().contains('exit code: 1') { - // // Exit code 1 means the remote commit is not in our history - // // Therefore we need to pull - // return true - // } - return true - // return error('Failed to check merge-base: ${err}') - } - // If we get here, the remote commit is in our history - // Therefore we don't need to pull - return false + return repo.status.behind > 0 } -// Legacy function for backward compatibility +// need_push_or_pull is a convenience function. pub fn (mut repo GitRepo) need_push_or_pull() !bool { + repo.status_update()! return repo.need_push()! || repo.need_pull()! } -// Determine if the repository needs to checkout to a different branch or tag -fn (mut repo GitRepo) need_checkout() bool { - if repo.status_wanted.branch.len > 0 { - if repo.status_wanted.branch != repo.status_local.branch { - return true - } - } else if repo.status_wanted.tag.len > 0 { - if repo.status_wanted.tag != repo.status_local.tag { - return true - } - } - // it could be empty since the status_wanted are optional. - // else{ - // panic("bug, should never be empty ${repo.status_wanted.branch}, ${repo.status_local.branch}") - // } - return false -} - -fn (mut repo GitRepo) get_remote_default_branchname() !string { - if repo.status_remote.ref_default.len == 0 { - return error('ref_default cannot be empty for ${repo.path()}') - } - - return repo.status_remote.branches[repo.status_remote.ref_default] or { - return error("can't find ref_default in branches for ${repo.path()}") - } -} - -// is always the commit for the branch as known remotely, if not known will return "" +// get_last_remote_commit gets the commit hash for the current branch as known on the remote. pub fn (self GitRepo) get_last_remote_commit() !string { - if self.status_local.branch in self.status_remote.branches { - return self.status_remote.branches[self.status_local.branch] - } - - return '' + // The branch map contains both local and remote refs, normalized by name. + return self.status.branches[self.status.branch] or { '' } } -// get commit for branch, will return '' if local branch doesn't exist remotely +// get_last_local_commit gets the commit hash for the current local branch. pub fn (self GitRepo) get_last_local_commit() !string { - if self.status_local.branch in self.status_local.branches { - return self.status_local.branches[self.status_local.branch] - } - return '' + return self.exec('git rev-parse HEAD')! } diff --git a/lib/develop/gittools/repository_load.v b/lib/develop/gittools/repository_load.v index bd8ea320..6f898642 100644 --- a/lib/develop/gittools/repository_load.v +++ b/lib/develop/gittools/repository_load.v @@ -6,93 +6,93 @@ import os @[params] pub struct StatusUpdateArgs { - reload bool - force bool // Add force flag to bypass cache when callers need it. + reset bool } pub fn (mut repo GitRepo) status_update(args StatusUpdateArgs) ! { - // Clear previous errors - repo.status_local.error = '' - repo.status_remote.error = '' + repo.init()! - // Check current time vs last check, if needed (check period) then load - repo.cache_get() or { - repo.status_local.error = 'Failed to get cache: ${err}' - return error('Failed to get cache for repo ${repo.name}: ${err}') - } // Ensure we have the situation from redis - repo.init() or { - repo.status_local.error = 'Failed to initialize: ${err}' - return error('Failed to initialize repo ${repo.name}: ${err}') - } - - // If there's an existing error, skip loading and just return. - // This prevents repeated attempts to load a problematic repo. - if repo.status_local.error.len > 0 || repo.status_remote.error.len > 0 { - console.print_debug('Skipping load for ${repo.name} due to existing error.') + // Skip remote checks if offline. + if repo.gs.offline { + console.print_debug('status update skipped (offline) for ${repo.path()}') return } - if 'OFFLINE' in os.environ() || (repo.gs.config()!.offline) { - console.print_debug('fetch skipped (offline)') - return + if args.reset || repo.last_load == 0 { + // console.print_debug('${repo.name} : Cache get') + repo.cache_get()! } + + // cacheexists:=repo.cache_exists()! + // console.print_debug('${repo.name} : Checking if a full load is needed for cacheexists:${cacheexists} ') + current_time := int(time.now().unix()) - if args.reload || repo.last_load == 0 + // Decide if a full load is needed. + if args.reset || repo.last_load == 0 || current_time - repo.last_load >= repo.config.remote_check_period { - // console.print_debug('${repo.name} ${current_time}-${repo.last_load} (${current_time - repo.last_load >= repo.config.remote_check_period}): ${repo.config.remote_check_period} +++') - // if true{exit(0)} - repo.load() or { - repo.status_remote.error = 'Failed to load repository: ${err}' + repo.load_internal() or { + // Persist the error state to the cache + console.print_stderr('Failed to load repository ${repo.name} at ${repo.path()}: ${err}') + if repo.status.error == '' { + repo.status.error = 'Failed to load repository: ${err}' + } return error('Failed to load repository ${repo.name}: ${err}') } + repo.cache_set()! + // $dbg; } } -// Load repo information -// Does not check cache, it is the callers responsibility to check cache and load accordingly. -fn (mut repo GitRepo) load() ! { - console.print_header('load ${repo.print_key()}') - repo.init() or { - repo.status_local.error = 'Failed to initialize repo during load operation: ${err}' - return error('Failed to initialize repo during load operation: ${err}') - } +// load_internal performs the expensive git operations to refresh the repository state. +// It should only be called by status_update(). +fn (mut repo GitRepo) load_internal() ! { + console.print_debug('load ${repo.print_key()}') + repo.init()! - git_path := '${repo.path()}/.git' - if os.exists(git_path) == false { - repo.status_local.error = 'Repository not found: missing .git directory' - return error('Repository not found: ${repo.path()} is not a valid git repository (missing .git directory)') - } - - repo.exec('git fetch --all') or { - repo.status_remote.error = 'Failed to fetch updates: ${err}' + repo.exec('fetch --all') or { + repo.status.error = 'Failed to fetch updates: ${err}' return error('Failed to fetch updates for ${repo.name} at ${repo.path()}: ${err}. Please check network connection and repository access.') } + repo.load_branches()! + repo.load_tags()! - repo.load_branches() or { - repo.status_remote.error = 'Failed to load branches: ${err}' - return error('Failed to load branches for ${repo.name}: ${err}') - } + // Reset ahead/behind counts before recalculating + repo.status.ahead = 0 + repo.status.behind = 0 - repo.load_tags() or { - repo.status_remote.error = 'Failed to load tags: ${err}' - return error('Failed to load tags for ${repo.name}: ${err}') + // Get ahead/behind information for the current branch + status_res := repo.exec('status --porcelain=v2 --branch')! + for line in status_res.split_into_lines() { + if line.starts_with('# branch.ab') { + parts := line.split(' ') + if parts.len > 3 { + ahead_str := parts[2] + behind_str := parts[3] + if ahead_str.starts_with('+') { + repo.status.ahead = ahead_str[1..].int() + } + if behind_str.starts_with('-') { + repo.status.behind = behind_str[1..].int() + } + } + break // We only need this one line + } } repo.last_load = int(time.now().unix()) - repo.has_changes = repo.detect_changes() or { - repo.status_local.error = 'Failed to detect changes: ${err}' + repo.status.has_changes = repo.detect_changes() or { + repo.status.error = 'Failed to detect changes: ${err}' return error('Failed to detect changes in repository ${repo.name}: ${err}') } - repo.cache_set() or { - repo.status_local.error = 'Failed to update cache: ${err}' - return error('Failed to update cache for repository ${repo.name}: ${err}') - } + + // Persist the newly loaded state to the cache. + repo.cache_set()! } // Helper to load remote tags fn (mut repo GitRepo) load_branches() ! { - tags_result := repo.exec("git for-each-ref --format='%(objectname) %(refname:short)' refs/heads refs/remotes/origin") or { + tags_result := repo.exec("for-each-ref --format='%(objectname) %(refname:short)' refs/heads refs/remotes/origin") or { return error('Failed to get branch references: ${err}. Command: git for-each-ref') } for line in tags_result.split('\n') { @@ -109,22 +109,22 @@ fn (mut repo GitRepo) load_branches() ! { if name.contains('_archive') { continue } else if name == 'origin' { - repo.status_remote.ref_default = commit_hash + // No longer storing ref_default separately, it's implied by the branch map } else if name.starts_with('origin') { name = name.all_after('origin/').trim_space() - // Update remote tags info - repo.status_remote.branches[name] = commit_hash + // Update branches info + repo.status.branches[name] = commit_hash } else { - repo.status_local.branches[name] = commit_hash + repo.status.branches[name] = commit_hash } } } - mybranch := repo.exec('git branch --show-current') or { + mybranch := repo.exec('branch --show-current') or { return error('Failed to get current branch: ${err}') }.split_into_lines().filter(it.trim_space() != '') if mybranch.len == 1 { - repo.status_local.branch = mybranch[0].trim_space() + repo.status.branch = mybranch[0].trim_space() } else { return error('bug: git branch does not give branchname.\n${mybranch}') } @@ -132,23 +132,65 @@ fn (mut repo GitRepo) load_branches() ! { // Helper to load remote tags fn (mut repo GitRepo) load_tags() ! { - tags_result := repo.exec('git tag --list') or { + // CORRECTED: Use for-each-ref to get commit hashes for tags. + tags_result := repo.exec("for-each-ref --format='%(objectname) %(refname:short)' refs/tags") or { return error('Failed to list tags: ${err}. Please ensure git is installed and repository is accessible.') } - // println(tags_result) + for line in tags_result.split('\n') { line_trimmed := line.trim_space() if line_trimmed != '' { parts := line_trimmed.split(' ') if parts.len < 2 { - // console.print_debug('Skipping malformed tag line: ${line_trimmed}') - continue + continue // Skip malformed lines } commit_hash := parts[0].trim_space() - tag_name := parts[1].all_after('refs/tags/').trim_space() + // refname:short for tags is just the tag name itself. + tag_name := parts[1].trim_space() // Update remote tags info - repo.status_remote.tags[tag_name] = commit_hash + repo.status.tags[tag_name] = commit_hash } } } + +// Retrieves a list of unstaged changes in the repository. +// +// This function returns a list of files that are modified or untracked. +// +// Returns: +// - An array of strings representing file paths of unstaged changes. +// - Throws an error if the command execution fails. +pub fn (repo GitRepo) get_changes_unstaged() ![]string { + unstaged_result := repo.exec('ls-files --other --modified --exclude-standard') or { + return error('Failed to check for unstaged changes: ${repo.path()}\n${err}') + } + + // Filter out any empty lines from the result. + return unstaged_result.split('\n').filter(it.len > 0) +} + +// Retrieves a list of staged changes in the repository. +// +// This function returns a list of files that are staged and ready to be committed. +// +// Returns: +// - An array of strings representing file paths of staged changes. +// - Throws an error if the command execution fails. +pub fn (repo GitRepo) get_changes_staged() ![]string { + staged_result := repo.exec('diff --name-only --staged') or { + return error('Failed to check for staged changes: ${repo.path()}\n${err}') + } + // Filter out any empty lines from the result. + return staged_result.split('\n').filter(it.len > 0) +} + +// Check if there are any unstaged or untracked changes in the repository. +pub fn (mut repo GitRepo) detect_changes() !bool { + r0 := repo.get_changes_unstaged()! + r1 := repo.get_changes_staged()! + if r0.len + r1.len > 0 { + return true + } + return false +} diff --git a/lib/develop/gittools/repository_model.v b/lib/develop/gittools/repository_model.v new file mode 100644 index 00000000..261edef7 --- /dev/null +++ b/lib/develop/gittools/repository_model.v @@ -0,0 +1,41 @@ +module gittools + +// GitRepo represents a single git repository. +@[heap] +pub struct GitRepo { + // a git repo is always part of a git structure +mut: + gs &GitStructure @[skip; str: skip] + last_load int // epoch when last loaded +pub mut: + provider string // e.g., github.com + account string // Git account name + name string // Repository name + deploysshkey string // SSH key for git operations + config GitRepoConfig + status GitStatus +} + +// GitStatus holds the unified status information for a repository. +// It reflects the CURRENT state, not a desired state. +pub struct GitStatus { +pub mut: + // Combined local & remote state (from fetch) + branches map[string]string // All branch names -> commit hash + tags map[string]string // All tag names -> commit hash + + // Current local state + branch string // The current checked-out branch. + tag string // The current checked-out tag (if any). + ahead int // Commits ahead of remote. + behind int // Commits behind remote. + + // Combined status + has_changes bool // True if there are uncommitted local changes. + error string // Error message if any status update fails. +} + +pub struct GitRepoConfig { +pub mut: + remote_check_period int = 3600 // seconds = 1h +} diff --git a/lib/develop/reprompt/templates/prompt_example.md b/lib/develop/reprompt/templates/prompt_example.md new file mode 100644 index 00000000..0675fea0 --- /dev/null +++ b/lib/develop/reprompt/templates/prompt_example.md @@ -0,0 +1,1607 @@ + +/Users/despiegk/code/github/freeflowuniverse/herolib +├── .gitignore +├── .vdocignore +├── compile.sh +├── CONTRIBUTING.md +├── doc.vsh +├── generate.vsh +├── herolib.code-workspace +├── install_hero.sh +├── install_herolib.vsh +├── install_v.sh +├── LICENSE +├── README.md +├── release_OLD.sh +├── release.vsh +├── specs.md +├── test_basic.vsh +└── test_runner.vsh +├── .github +│ └── workflows +│ ├── documentation.yml +│ ├── hero_build.yml +│ └── test.yml +├── aiprompts +│ └── herolib_start_here.md +│ ├── .openhands +│ │ └── setup.sh +│ ├── ai_instruct +│ │ ├── documentation_from_v_md.md +│ │ ├── documentation_from_v.md +│ │ ├── prompt_processing_instructions.md +│ │ ├── prompt_processing_openrpc_like.md +│ │ └── what_is_a_hero_twin.md +│ │ ├── models_from_v +│ ├── bizmodel +│ │ ├── bizmodel_cost.md +│ │ ├── bizmodel_funding.md +│ │ ├── bizmodel_generation_prompt.md +│ │ ├── bizmodel_hr.md +│ │ ├── bizmodel_revenue.md +│ │ └── costs.heroscript +│ ├── documentor +│ │ └── generate_v_doc_readable_md_files.md +│ ├── docusaurus +│ │ └── docusaurus_ebook_manual.md +│ ├── herolib_advanced +│ │ ├── advanced_paths.md +│ │ ├── builder.md +│ │ ├── cmdline_argument_parsing_example.vsh +│ │ ├── datatypes.md +│ │ ├── osal.md +│ │ ├── ourdb.md +│ │ ├── redis.md +│ │ ├── spreadsheet.md +│ │ └── ui console chalk.md +│ ├── herolib_core +│ │ ├── core_curdir_example.md +│ │ ├── core_globals.md +│ │ ├── core_heroscript_basics.md +│ │ ├── core_heroscript_playbook.md +│ │ ├── core_http_client.md +│ │ ├── core_osal.md +│ │ ├── core_ourtime.md +│ │ ├── core_params.md +│ │ ├── core_paths.md +│ │ ├── core_text.md +│ │ ├── core_ui_console.md +│ │ └── core_vshell.md +│ ├── v_advanced +│ │ ├── advanced_topics.md +│ │ ├── compress.md +│ │ ├── generics.md +│ │ ├── html_parser.md +│ │ ├── io.md +│ │ ├── net.md +│ │ ├── reflection.md +│ │ ├── regex.md +│ │ ├── smtp.md +│ │ └── time instructions.md +│ ├── v_core +│ │ └── v_manual.md +│ ├── v_veb_webserver +│ │ ├── veb_assets.md +│ │ ├── veb_auth.md +│ │ ├── veb_csrf.md +│ │ ├── veb_sse.md +│ │ ├── veb.md +│ │ └── vtemplates.md +├── cli +│ ├── .gitignore +│ ├── compile +│ ├── compile_upload.vsh +│ ├── compile_vdo.vsh +│ ├── compile.vsh +│ ├── hero.v +│ └── vdo.v +├── docker +│ └── docker_ubuntu_install.sh +│ ├── herolib +│ │ ├── .gitignore +│ │ ├── build.sh +│ │ ├── debug.sh +│ │ ├── docker-compose.yml +│ │ ├── Dockerfile +│ │ ├── export.sh +│ │ ├── README.md +│ │ ├── shell.sh +│ │ ├── ssh_init.sh +│ │ ├── ssh.sh +│ │ └── start.sh +│ │ ├── scripts +│ ├── postgresql +│ │ ├── docker-compose.yml +│ │ ├── readme.md +│ │ └── start.sh +├── examples +│ ├── README.md +│ └── sync_do.sh +│ ├── aiexamples +│ │ ├── groq.vsh +│ │ ├── jetconvertor.vsh +│ │ ├── jina.vsh +│ │ └── qdrant.vsh +│ ├── baobab +│ │ ├── generator +│ │ └── specification +│ ├── biztools +│ │ ├── bizmodel_complete.vsh +│ │ ├── bizmodel_export.vsh +│ │ ├── bizmodel_full.vsh +│ │ ├── bizmodel.vsh +│ │ ├── bizmodel1.vsh +│ │ ├── bizmodel2.vsh +│ │ ├── costs.vsh +│ │ ├── funding.vsh +│ │ ├── hr.vsh +│ │ └── notworking.md +│ │ ├── _archive +│ │ ├── bizmodel_docusaurus +│ │ ├── examples +│ ├── builder +│ │ ├── simple_ip4.vsh +│ │ ├── simple_ip6.vsh +│ │ └── simple.vsh +│ │ ├── remote_executor +│ ├── clients +│ │ ├── aiclient_example.vsh +│ │ ├── jina_example.vsh +│ │ ├── mail.vsh +│ │ ├── mycelium_rpc.vsh +│ │ ├── mycelium.vsh +│ │ ├── psql.vsh +│ │ └── zinit_rpc_example.vsh +│ ├── core +│ │ ├── agent_encoding.vsh +│ │ ├── generate.vsh +│ │ └── secrets_example.vsh +│ │ ├── base +│ │ ├── db +│ │ ├── dbfs +│ │ ├── openapi +│ │ ├── openrpc +│ │ ├── pathlib +│ ├── data +│ │ ├── .gitignore +│ │ ├── cache.vsh +│ │ ├── compress_gzip_example.vsh +│ │ ├── deduped_mycelium_master.vsh +│ │ ├── deduped_mycelium_worker.vsh +│ │ ├── encoder.vsh +│ │ ├── encrypt_decrypt.vsh +│ │ ├── graphdb.vsh +│ │ ├── heroencoder_example.vsh +│ │ ├── heroencoder_simple.vsh +│ │ ├── jsonexample.vsh +│ │ ├── ourdb_client.vsh +│ │ ├── ourdb_example.vsh +│ │ ├── ourdb_server.vsh +│ │ ├── radixtree.vsh +│ │ └── regex_example.vsh +│ │ ├── location +│ │ ├── ourdb_syncer +│ │ ├── params +│ │ ├── resp +│ ├── develop +│ │ ├── gittools +│ │ ├── ipapi +│ │ ├── juggler +│ │ ├── luadns +│ │ ├── openai +│ │ ├── runpod +│ │ ├── vastai +│ │ └── wireguard +│ ├── hero +│ │ └── alpine_example.vsh +│ │ ├── db +│ │ ├── generation +│ │ ├── openapi +│ ├── installers +│ │ ├── .gitignore +│ │ ├── cometbft.vsh +│ │ ├── conduit.vsh +│ │ ├── coredns.vsh +│ │ ├── hero_install.vsh +│ │ ├── installers.vsh +│ │ ├── traefik.vsh +│ │ └── youki.vsh +│ │ ├── db +│ │ ├── infra +│ │ ├── lang +│ │ ├── net +│ │ ├── sysadmintools +│ │ ├── threefold +│ │ ├── virt +│ ├── jobs +│ │ └── vfs_jobs_example.vsh +│ ├── lang +│ │ └── python +│ ├── mcp +│ │ ├── http_demo +│ │ ├── http_server +│ │ ├── inspector +│ │ └── simple_http +│ ├── osal +│ │ ├── .gitignore +│ │ ├── notifier.vsh +│ │ ├── startup_manager.vsh +│ │ ├── systemd.vsh +│ │ ├── tun.vsh +│ │ ├── ufw_play.vsh +│ │ └── ufw.vsh +│ │ ├── coredns +│ │ ├── download +│ │ ├── ping +│ │ ├── process +│ │ ├── rsync +│ │ ├── sandbox +│ │ ├── sshagent +│ │ ├── zinit +│ ├── schemas +│ │ ├── openapi +│ │ └── openrpc +│ ├── threefold +│ │ └── .gitignore +│ │ ├── grid +│ │ ├── gridproxy +│ │ ├── holochain +│ │ ├── solana +│ │ ├── tfgrid3deployer +│ ├── tools +│ │ ├── imagemagick +│ │ ├── tmux +│ │ └── vault +│ ├── ui +│ │ ├── flow1.v +│ │ └── silence.vsh +│ │ ├── console +│ │ ├── telegram +│ ├── vfs +│ │ └── vfs_db +│ ├── virt +│ │ ├── daguserver +│ │ ├── docker +│ │ ├── hetzner +│ │ ├── lima +│ │ ├── podman_buildah +│ │ ├── runc +│ │ └── windows +│ ├── web +│ │ ├── .gitignore +│ │ ├── docusaurus_example.vsh +│ │ ├── starllight_example.vsh +│ │ └── ui_demo.vsh +│ │ ├── doctree +│ │ ├── markdown_renderer +│ ├── webdav +│ │ ├── .gitignore +│ │ └── webdav_vfs.vsh +├── lib +│ ├── readme.md +│ └── v.mod +│ ├── ai +│ │ ├── escalayer +│ │ ├── mcp +│ │ └── utils +│ ├── baobab +│ │ └── README.md +│ │ ├── actor +│ │ ├── generator +│ │ ├── osis +│ │ ├── specification +│ │ ├── stage +│ ├── biz +│ │ ├── bizmodel +│ │ ├── investortool +│ │ ├── planner +│ │ └── spreadsheet +│ ├── builder +│ │ ├── bootstrapper.v +│ │ ├── builder_factory.v +│ │ ├── done.v +│ │ ├── executor_local_test.v +│ │ ├── executor_local.v +│ │ ├── executor_ssh_test.v +│ │ ├── executor_ssh.v +│ │ ├── executor.v +│ │ ├── model_package.v +│ │ ├── node_commands.v +│ │ ├── node_executor.v +│ │ ├── node_factory.v +│ │ ├── node.v +│ │ ├── nodedb_test.v +│ │ ├── portforward_lib.v +│ │ ├── readme.md +│ │ └── this_remote.v +│ ├── clients +│ │ ├── ipapi +│ │ ├── jina +│ │ ├── livekit +│ │ ├── mailclient +│ │ ├── meilisearch +│ │ ├── mycelium +│ │ ├── mycelium_rpc +│ │ ├── openai +│ │ ├── postgresql_client +│ │ ├── qdrant +│ │ ├── rclone +│ │ ├── runpod +│ │ ├── sendgrid +│ │ ├── vastai +│ │ ├── wireguard +│ │ ├── zerodb_client +│ │ ├── zinit +│ │ └── zinit_rpc +│ ├── code +│ │ └── generator +│ ├── conversiontools +│ │ └── tools.v +│ │ ├── docsorter +│ │ ├── imagemagick +│ │ ├── pdftotext +│ │ ├── text_extractor +│ ├── core +│ │ ├── interactive.v +│ │ ├── memdb_test.v +│ │ ├── memdb.v +│ │ ├── platform_test.v +│ │ ├── platform.v +│ │ ├── readme.md +│ │ ├── sudo_test.v +│ │ └── sudo.v +│ │ ├── base +│ │ ├── code +│ │ ├── generator +│ │ ├── herocmds +│ │ ├── httpconnection +│ │ ├── logger +│ │ ├── openrpc_remove +│ │ ├── pathlib +│ │ ├── playbook +│ │ ├── playcmds +│ │ ├── playmacros +│ │ ├── redisclient +│ │ ├── rootpath +│ │ ├── smartid +│ │ ├── texttools +│ │ ├── vexecutor +│ ├── crypt +│ │ └── crypt.v +│ │ ├── aes_symmetric +│ │ ├── crpgp +│ │ ├── ed25519 +│ │ ├── keychain +│ │ ├── keysafe +│ │ ├── openssl +│ │ ├── pgp +│ │ ├── secp256k1 +│ │ ├── secrets +│ ├── data +│ │ ├── cache +│ │ ├── currency +│ │ ├── dbfs +│ │ ├── dedupestor +│ │ ├── doctree +│ │ ├── encoder +│ │ ├── encoderhero +│ │ ├── flist +│ │ ├── gid +│ │ ├── graphdb +│ │ ├── ipaddress +│ │ ├── location +│ │ ├── markdown +│ │ ├── markdownparser2 +│ │ ├── markdownrenderer +│ │ ├── mnemonic +│ │ ├── models +│ │ ├── ourdb +│ │ ├── ourdb_syncer +│ │ ├── ourjson +│ │ ├── ourtime +│ │ ├── paramsparser +│ │ ├── radixtree +│ │ ├── resp +│ │ ├── serializers +│ │ ├── tst +│ │ ├── verasure +│ │ └── vstor +│ ├── dav +│ │ └── webdav +│ ├── develop +│ │ ├── gittools +│ │ ├── luadns +│ │ ├── performance +│ │ ├── sourcetree +│ │ ├── vscode +│ │ └── vscode_extensions +│ ├── hero +│ │ └── models +│ │ ├── db +│ ├── installers +│ │ ├── install_multi.v +│ │ └── upload.v +│ │ ├── base +│ │ ├── db +│ │ ├── develapps +│ │ ├── infra +│ │ ├── lang +│ │ ├── net +│ │ ├── sysadmintools +│ │ ├── threefold +│ │ ├── ulist +│ │ ├── virt +│ │ ├── web +│ ├── lang +│ │ ├── python +│ │ └── rust +│ ├── mcp +│ │ ├── backend_interface.v +│ │ ├── backend_memory.v +│ │ ├── factory.v +│ │ ├── generics.v +│ │ ├── handler_initialize_test.v +│ │ ├── handler_initialize.v +│ │ ├── handler_prompts.v +│ │ ├── handler_resources.v +│ │ ├── handler_tools.v +│ │ ├── model_configuration_test.v +│ │ ├── model_configuration.v +│ │ ├── model_error.v +│ │ ├── README.md +│ │ └── server.v +│ │ ├── baobab +│ │ ├── cmd +│ │ ├── mcpgen +│ │ ├── pugconvert +│ │ ├── rhai +│ │ ├── transport +│ │ ├── vcode +│ ├── osal +│ │ ├── core +│ │ ├── coredns +│ │ ├── hostsfile +│ │ ├── notifier +│ │ ├── osinstaller +│ │ ├── rsync +│ │ ├── screen +│ │ ├── sshagent +│ │ ├── startupmanager +│ │ ├── systemd +│ │ ├── tmux +│ │ ├── traefik +│ │ ├── tun +│ │ ├── ufw +│ │ └── zinit +│ ├── schemas +│ │ ├── jsonrpc +│ │ ├── jsonschema +│ │ ├── openapi +│ │ └── openrpc +│ ├── security +│ │ ├── authentication +│ │ └── jwt +│ ├── threefold +│ │ ├── grid3 +│ │ └── grid4 +│ ├── ui +│ │ ├── factory.v +│ │ └── readme.md +│ │ ├── console +│ │ ├── generic +│ │ ├── logger +│ │ ├── telegram +│ │ ├── template +│ │ ├── uimodel +│ ├── vfs +│ │ ├── interface.v +│ │ ├── metadata.v +│ │ └── README.md +│ │ ├── vfs_calendar +│ │ ├── vfs_contacts +│ │ ├── vfs_db +│ │ ├── vfs_local +│ │ ├── vfs_mail +│ │ ├── vfs_nested +│ ├── virt +│ │ ├── cloudhypervisor +│ │ ├── docker +│ │ ├── herocontainers +│ │ ├── hetzner +│ │ ├── lima +│ │ ├── qemu +│ │ ├── runc +│ │ └── utils +│ ├── web +│ │ ├── doctreeclient +│ │ ├── docusaurus +│ │ ├── echarts +│ │ ├── site +│ │ └── ui +├── libarchive +│ ├── installers +│ │ └── web +│ ├── rhai +│ │ ├── generate_rhai_example.v +│ │ ├── generate_wrapper_module.v +│ │ ├── register_functions.v +│ │ ├── register_types_test.v +│ │ ├── register_types.v +│ │ ├── rhai_test.v +│ │ ├── rhai.v +│ │ └── verify.v +│ │ ├── prompts +│ │ ├── templates +│ │ ├── testdata +│ └── starlight +│ ├── clean.v +│ ├── config.v +│ ├── factory.v +│ ├── model.v +│ ├── site_get.v +│ ├── site.v +│ ├── template.v +│ └── watcher.v +│ ├── templates +├── manual +│ ├── config.json +│ ├── create_tag.md +│ └── serve_wiki.sh +│ ├── best_practices +│ │ └── using_args_in_function.md +│ │ ├── osal +│ │ ├── scripts +│ ├── core +│ │ ├── base.md +│ │ ├── context_session_job.md +│ │ ├── context.md +│ │ ├── play.md +│ │ └── session.md +│ │ ├── concepts +│ ├── documentation +│ │ └── docextractor.md +├── research +│ └── globals +│ ├── globals_example_inplace.vsh +│ ├── globals_example_reference.vsh +│ ├── globals_example.vsh +│ └── ubuntu_partition.sh +├── vscodeplugin +│ ├── install_ubuntu.sh +│ ├── package.sh +│ └── readme.md +│ ├── heroscrypt-syntax +│ │ ├── heroscript-syntax-0.0.1.vsix +│ │ ├── language-configuration.json +│ │ └── package.json +│ │ ├── syntaxes + + + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/bizmodel.v +```v +module playcmds + +// import freeflowuniverse.herolib.core.playbook + +// fn git(mut actions playbook.Actions, action playbook.Action) ! { +// if action.name == 'init' { +// // means we support initialization afterwards +// c.bizmodel_init(mut actions, action)! +// } + +// // if action.name == 'get' { +// // mut gs := gittools.new()! +// // url := action.params.get('url')! +// // branch := action.params.get_default('branch', '')! +// // reset := action.params.get_default_false('reset')! +// // pull := action.params.get_default_false('pull')! +// // mut gr := gs.repo_get_from_url(url: url, branch: branch, pull: pull, reset: reset)! +// // } +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/currency.v +```v +module playcmds + +// fn currency_actions(actions_ []playbook.Action) ! { +// mut actions2 := actions.filtersort(actions: actions_, actor: 'currency', book: '*')! +// if actions2.len == 0 { +// return +// } + +// mut cs := currency.new()! + +// for action in actions2 { +// // TODO: set the currencies +// if action.name == 'default_set' { +// cur := action.params.get('cur')! +// usdval := action.params.get_int('usdval')! +// cs.default_set(cur, usdval)! +// } +// } + +// // TODO: add the currency metainfo, do a test +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/dagu.v +```v +module playcmds + +// import freeflowuniverse.herolib.installers.sysadmintools.daguserver + +// pub fn scheduler(heroscript string) ! { +// daguserver.play( +// heroscript: heroscript +// )! +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/downloader.v +```v +module playcmds + +// import freeflowuniverse.herolib.core.playbook +// import freeflowuniverse.herolib.sysadmin.downloader + +// can start with sal, dal, ... the 2nd name is typicall the actor (or topic) +// do this function public and then it breaches out to detail functionality + +// pub fn sal_downloader(action playbook.Action) ! { +// match action.actor { +// 'downloader' { +// match action.name { +// 'get' { +// downloader_get(action: action)! +// } +// else { +// return error('actions not supported yet') +// } +// } +// } +// else { +// return error('actor not supported yet') +// } +// } +// } + +// fn downloader_get(args ActionExecArgs) ! { +// action := args.action +// // session:=args.action or {panic("no context")} //if we need it here +// mut name := action.params.get_default('name', '')! +// mut downloadpath := action.params.get_default('downloadpath', '')! +// mut url := action.params.get_default('url', '')! +// mut reset := action.params.get_default_false('reset') +// mut gitpull := action.params.get_default_false('gitpull') + +// mut minsize_kb := action.params.get_u32_default('minsize_kb', 0)! +// mut maxsize_kb := action.params.get_u32_default('maxsize_kb', 0)! + +// mut destlink := action.params.get_default_false('destlink') + +// mut dest := action.params.get_default('dest', '')! +// mut hash := action.params.get_default('hash', '')! +// mut metapath := action.params.get_default('metapath', '')! + +// mut meta := downloader.download( +// name: name +// downloadpath: downloadpath +// url: url +// reset: reset +// gitpull: gitpull +// minsize_kb: minsize_kb +// maxsize_kb: maxsize_kb +// destlink: destlink +// dest: dest +// hash: hash +// metapath: metapath +// // session:session // TODO IMPLEMENT (also optional) +// )! +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_caddy.v +```v +module playcmds + +// import freeflowuniverse.herolib.installers.web.caddy as caddy_installer +// import freeflowuniverse.herolib.servers.caddy { CaddyFile } +// import freeflowuniverse.herolib.core.playbook +// import os +// // import net.urllib + +// pub fn play_caddy(mut plbook playbook.PlayBook) ! { +// play_caddy_basic(mut plbook)! +// play_caddy_configure(mut plbook)! +// } + +// pub fn play_caddy_configure(mut plbook playbook.PlayBook) ! { +// mut caddy_actions := plbook.find(filter: 'caddy_configure')! +// if caddy_actions.len == 0 { +// return +// } +// } + +// pub fn play_caddy_basic(mut plbook playbook.PlayBook) ! { +// caddy_actions := plbook.find(filter: 'caddy.')! +// if caddy_actions.len == 0 { +// return +// } + +// mut install_actions := plbook.find(filter: 'caddy.install')! + +// if install_actions.len > 0 { +// for install_action in install_actions { +// mut p := install_action.params +// xcaddy := p.get_default_false('xcaddy') +// file_path := p.get_default('file_path', '/etc/caddy')! +// file_url := p.get_default('file_url', '')! +// reset := p.get_default_false('reset') +// start := p.get_default_false('start') +// restart := p.get_default_false('restart') +// stop := p.get_default_false('stop') +// homedir := p.get_default('file_url', '')! +// plugins := p.get_list_default('plugins', []string{})! + +// caddy_installer.install( +// xcaddy: xcaddy +// file_path: file_path +// file_url: file_url +// reset: reset +// start: start +// restart: restart +// stop: stop +// homedir: homedir +// plugins: plugins +// )! +// } +// } + +// mut config_actions := plbook.find(filter: 'caddy.configure')! +// if config_actions.len > 0 { +// mut coderoot := '' +// mut reset := false +// mut pull := false + +// mut public_ip := '' + +// mut c := caddy.get('')! +// // that to me seems to be wrong, not generic enough +// if config_actions.len > 1 { +// return error('can only have 1 config action for books') +// } else if config_actions.len == 1 { +// mut p := config_actions[0].params +// path := p.get_default('path', '/etc/caddy')! +// url := p.get_default('url', '')! +// public_ip = p.get_default('public_ip', '')! +// c = caddy.configure('', homedir: path)! +// config_actions[0].done = true +// } + +// mut caddyfile := CaddyFile{} +// for mut action in plbook.find(filter: 'caddy.add_reverse_proxy')! { +// mut p := action.params +// mut from := p.get_default('from', '')! +// mut to := p.get_default('to', '')! + +// if from == '' || to == '' { +// return error('from & to cannot be empty') +// } + +// caddyfile.add_reverse_proxy( +// from: from +// to: to +// )! +// action.done = true +// } + +// for mut action in plbook.find(filter: 'caddy.add_file_server')! { +// mut p := action.params +// mut domain := p.get_default('domain', '')! +// mut root := p.get_default('root', '')! + +// if root.starts_with('~') { +// root = '${os.home_dir()}${root.trim_string_left('~')}' +// } + +// if domain == '' || root == '' { +// return error('domain & root cannot be empty') +// } + +// caddyfile.add_file_server( +// domain: domain +// root: root +// )! +// action.done = true +// } + +// for mut action in plbook.find(filter: 'caddy.add_basic_auth')! { +// mut p := action.params +// mut domain := p.get_default('domain', '')! +// mut username := p.get_default('username', '')! +// mut password := p.get_default('password', '')! + +// if domain == '' || username == '' || password == '' { +// return error('domain & root cannot be empty') +// } + +// caddyfile.add_basic_auth( +// domain: domain +// username: username +// password: password +// )! +// action.done = true +// } + +// for mut action in plbook.find(filter: 'caddy.generate')! { +// c.set_caddyfile(caddyfile)! +// action.done = true +// } + +// for mut action in plbook.find(filter: 'caddy.start')! { +// c.start()! +// action.done = true +// } +// c.reload()! +// } +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_dagu_test.v +```v +module playcmds + +import freeflowuniverse.herolib.core.playbook + +const dagu_script = " +!!dagu.configure + instance: 'test' + username: 'admin' + password: 'testpassword' + +!!dagu.new_dag + name: 'test_dag' + +!!dagu.add_step + dag: 'test_dag' + name: 'hello_world' + command: 'echo hello world' + +!!dagu.add_step + dag: 'test_dag' + name: 'last_step' + command: 'echo last step' + + +" + +fn test_play_dagu() ! { + mut plbook := playbook.new(text: dagu_script)! + play_dagu(mut plbook)! + // panic('s') +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_dagu.v +```v +module playcmds + +// import freeflowuniverse.herolib.clients.daguclient +// import freeflowuniverse.herolib.installers.sysadmintools.daguserver +// import freeflowuniverse.herolib.installers.sysadmintools.daguserver +import freeflowuniverse.herolib.core.playbook +import freeflowuniverse.herolib.ui.console +import os + +// pub fn play_dagu(mut plbook playbook.PlayBook) ! { +// // dagu_actions := plbook.find(filter: 'dagu.')! +// // if dagu_actions.len == 0 { +// // return +// // } + +// // play_dagu_basic(mut plbook)! +// // play_dagu_configure(mut plbook)! +// } + +// // play_dagu plays the dagu play commands +// pub fn play_dagu_basic(mut plbook playbook.PlayBook) ! { +// // mut install_actions := plbook.find(filter: 'daguserver.configure')! + +// // if install_actions.len > 0 { +// // for install_action in install_actions { +// // mut p := install_action.params +// // panic("daguinstall play") +// // } +// // } + +// // dagu_actions := plbook.find(filter: 'daguserver.install')! +// // if dagu_actions.len > 0 { +// // panic("daguinstall play") +// // return +// // } + +// // mut config_actions := plbook.find(filter: 'dagu.configure')! +// // mut d := if config_actions.len > 1 { +// // return error('can only have 1 config action for dagu') +// // } else if config_actions.len == 1 { +// // mut p := config_actions[0].params +// // instance := p.get_default('instance', 'default')! +// // port := p.get_int_default('port', 8888)! +// // username := p.get_default('username', '')! +// // password := p.get_default('password', '')! +// // config_actions[0].done = true +// // mut server := daguserver.configure(instance, +// // port: port +// // username: username +// // password: password +// // )! +// // server.start()! +// // console.print_debug('Dagu server is running at http://localhost:${port}') +// // console.print_debug('Username: ${username} password: ${password}') + +// // // configure dagu client with server url and api secret +// // server_cfg := server.config()! +// // daguclient.get(instance, +// // url: 'http://localhost:${port}' +// // apisecret: server_cfg.secret +// // )! +// // } else { +// // mut server := daguserver.get('')! +// // server.start()! +// // daguclient.get('')! +// // } + +// // mut dags := map[string]DAG{} + +// // for mut action in plbook.find(filter: 'dagu.new_dag')! { +// // mut p := action.params +// // name := p.get_default('name', '')! +// // dags[name] = DAG{} +// // action.done = true +// // } + +// // for mut action in plbook.find(filter: 'dagu.add_step')! { +// // mut p := action.params +// // dag := p.get_default('dag', 'default')! +// // name := p.get_default('name', 'default')! +// // command := p.get_default('command', '')! +// // dags[dag].step_add( +// // nr: dags.len +// // name: name +// // command: command +// // )! +// // } + +// // for mut action in plbook.find(filter: 'dagu.run')! { +// // mut p := action.params +// // dag := p.get_default('dag', 'default')! +// // // d.new_dag(dags[dag])! +// // panic('to implement') +// // } +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_juggler.v +```v +module playcmds + +import freeflowuniverse.herolib.data.doctree +import freeflowuniverse.herolib.ui.console +import freeflowuniverse.herolib.core.playbook +import freeflowuniverse.herolib.develop.juggler +import os + +pub fn play_juggler(mut plbook playbook.PlayBook) ! { + mut coderoot := '' + // mut install := false + mut reset := false + mut pull := false + + mut config_actions := plbook.find(filter: 'juggler.configure')! + + mut j := juggler.Juggler{} + + if config_actions.len > 1 { + return error('can only have 1 config action for juggler') + } else if config_actions.len == 1 { + mut p := config_actions[0].params + path := p.get_default('path', '/etc/juggler')! + url := p.get_default('url', '')! + username := p.get_default('username', '')! + password := p.get_default('password', '')! + port := p.get_int_default('port', 8000)! + + j = juggler.configure( + url: 'https://git.threefold.info/projectmycelium/itenv' + username: username + password: password + reset: true + )! + config_actions[0].done = true + } + + for mut action in plbook.find(filter: 'juggler.start')! { + j.start()! + action.done = true + } + + for mut action in plbook.find(filter: 'juggler.restart')! { + j.restart()! + action.done = true + } +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_publisher_test.v +```v +module playcmds + +import freeflowuniverse.herolib.core.playbook +import freeflowuniverse.herolib.core.playcmds +import freeflowuniverse.herolib.core.pathlib +import os + +fn test_play_publisher() { + mut p := pathlib.get_file(path: '/tmp/heroscript/do.hero', create: true)! + + s2 := " + +!!publisher.new_collection + url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/main/collections' + reset: false + pull: true + + +!!book.define + name:'info_tfgrid' + summary_url:'https://git.threefold.info/tfgrid/info_tfgrid/src/branch/development/books/tech/SUMMARY.md' + title:'ThreeFold Technology' + collections: 'about,dashboard,farmers,library,partners_utilization,tech,p2p' + + +!!book.publish + name:'tech' + production: false +" + p.write(s2)! + + mut plbook := playbook.new(path: '/tmp/heroscript')! + playcmds.play_publisher(mut plbook)! +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_publisher.v +```v +module playcmds + +import freeflowuniverse.herolib.core.playbook +// import freeflowuniverse.herolib.hero.publishing + +// pub fn play_publisher(mut plbook playbook.PlayBook) ! { +// publishing.play(mut plbook)! +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_threefold.v +```v +module playcmds + +import freeflowuniverse.herolib.core.playbook +// import freeflowuniverse.herolib.threefold.grid +// import freeflowuniverse.herolib.threefold.tfrobot +// import os + +// pub fn play_threefold(mut plbook playbook.PlayBook) ! { +// panic('fix tfrobot module') +// // mut config_actions := plbook.find(filter: 'threefold.configure')! + +// // mnemonics_ := os.getenv_opt('TFGRID_MNEMONIC') or { '' } +// // mut ssh_key := os.getenv_opt('SSH_KEY') or { '' } + +// // tfrobot.configure('play', network: 'main', mnemonics: mnemonics_)! + +// // mut robot := tfrobot.get('play')! + +// // if config_actions.len > 1 { +// // return error('can only have 1 config action for threefold') +// // } else if config_actions.len == 1 { +// // mut a := config_actions[0] +// // mut p := a.params +// // mut network := p.get_default('network', 'main')! +// // mnemonics := p.get_default('mnemonics', '')! +// // ssh_key = p.get_default('ssh_key', '')! + +// // network = network.to_lower() + +// // // mnemonics string +// // // network string = 'main' +// // tfrobot.configure('play', network: network, mnemonics: mnemonics)! + +// // robot = tfrobot.get('play')! + +// // config_actions[0].done = true +// // } +// // cfg := robot.config()! +// // if cfg.mnemonics == '' { +// // return error('TFGRID_MNEMONIC should be specified as env variable') +// // } + +// // if ssh_key == '' { +// // return error('SSHKey should be specified as env variable') +// // } + +// // panic('implement') + +// // for mut action in plbook.find(filter: 'threefold.deploy_vm')! { +// // mut p := action.params +// // deployment_name := p.get_default('deployment_name', 'deployment')! +// // name := p.get_default('name', 'vm')! +// // ssh_key := p.get_default('ssh_key', '')! +// // cores := p.get_int_default('cores', 1)! +// // memory := p.get_int_default('memory', 20)! +// // panic("implement") +// // action.done = true +// // } + +// // for mut action in plbook.find(filter: 'threefold.deploy_zdb')! { +// // panic("implement") +// // action.done = true +// // } +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/_archive/play_zola.v +```v +module playcmds + +// import freeflowuniverse.herolib.ui.console +// import freeflowuniverse.herolib.web.zola +// import freeflowuniverse.herolib.core.playbook + +// struct WebsiteItem { +// mut: +// name string +// site ?&zola.ZolaSite +// } + +// pub fn play_zola(mut plbook playbook.PlayBook) ! { +// // mut coderoot := '' +// mut buildroot := '' +// mut publishroot := '' +// mut install := true +// mut reset := false + +// wsactions := plbook.find(filter: 'website.')! +// if wsactions.len == 0 { +// return +// } + +// mut config_actions := plbook.find(filter: 'websites:configure')! +// if config_actions.len > 1 { +// return error('can only have 1 config action for websites') +// } else if config_actions.len == 1 { +// mut p := config_actions[0].params +// buildroot = p.get_default('buildroot', '')! +// publishroot = p.get_default('publishroot', '')! +// // coderoot = p.get_default('coderoot', '')! +// install = p.get_default_true('install') +// reset = p.get_default_false('reset') +// config_actions[0].done = true +// } +// mut websites := zola.new( +// path_build: buildroot +// path_publish: publishroot +// install: install +// reset: reset +// )! + +// mut ws := WebsiteItem{} + +// for mut action in plbook.find(filter: 'website.')! { +// if action.name == 'define' { +// console.print_debug('website.define') +// mut p := action.params +// ws.name = p.get('name')! +// title := p.get_default('title', '')! +// description := p.get_default('description', '')! +// ws.site = websites.new(name: ws.name, title: title, description: description)! +// } else if action.name == 'template_add' { +// console.print_debug('website.template_add') +// mut p := action.params +// url := p.get_default('url', '')! +// mut site_ := ws.site or { +// return error("can't find website for template_add, should have been defined before with !!website.define") +// } + +// site_.template_add(url: url)! +// } else if action.name == 'content_add' { +// console.print_debug('website.content_add') +// mut p := action.params +// url := p.get_default('url', '')! +// mut site_ := ws.site or { +// return error("can't find website for content_add, should have been defined before with !!website.define") +// } + +// site_.content_add(url: url)! +// } else if action.name == 'doctree_add' { +// console.print_debug('website.doctree_add') +// mut p := action.params +// url := p.get_default('url', '')! +// pull := p.get_default_false('pull') +// mut site_ := ws.site or { +// return error("can't find website for doctree_add, should have been defined before with !!website.define") +// } + +// site_.doctree_add(url: url, pull: pull)! +// } else if action.name == 'post_add' { +// console.print_debug('website.post_add') +// mut p := action.params +// name := p.get_default('name', '')! +// collection := p.get_default('collection', '')! +// file := p.get_default('file', '')! +// page := p.get_default('page', '')! +// pointer := p.get_default('pointer', '')! +// mut site_ := ws.site or { +// return error("can't find website for doctree_add, should have been defined before with !!website.define") +// } + +// site_.post_add(name: name, collection: collection, file: file, pointer: pointer)! +// } else if action.name == 'blog_add' { +// console.print_debug('website.blog_add') +// mut p := action.params +// name := p.get_default('name', '')! +// collection := p.get_default('collection', '')! +// file := p.get_default('file', '')! +// page := p.get_default('page', '')! +// pointer := p.get_default('pointer', '')! +// mut site_ := ws.site or { +// return error("can't find website for doctree_add, should have been defined before with !!website.define") +// } + +// site_.blog_add(name: name)! +// } else if action.name == 'person_add' { +// console.print_debug('website.person_add') +// mut p := action.params +// name := p.get_default('name', '')! +// page := p.get_default('page', '')! +// collection := p.get_default('collection', '')! +// file := p.get_default('file', '')! +// pointer := p.get_default('pointer', '')! +// mut site_ := ws.site or { +// return error("can't find website for doctree_add, should have been defined before with !!website.define") +// } + +// site_.person_add( +// name: name +// collection: collection +// file: file +// page: page +// pointer: pointer +// )! +// } else if action.name == 'people_add' { +// console.print_debug('website.people_add') +// mut p := action.params +// name := p.get_default('name', '')! +// description := p.get_default('description', '')! +// sort_by_ := p.get_default('sort_by', '')! +// mut site_ := ws.site or { +// return error("can't find website for people_add, should have been defined before with !!website.define") +// } + +// sort_by := zola.SortBy.from(sort_by_)! +// site_.people_add( +// name: name +// title: p.get_default('title', '')! +// sort_by: sort_by +// description: description +// )! +// } else if action.name == 'blog_add' { +// console.print_debug('website.blog_add') +// mut p := action.params +// name := p.get_default('name', '')! +// description := p.get_default('description', '')! +// sort_by_ := p.get_default('sort_by', '')! +// mut site_ := ws.site or { +// return error("can't find website for people_add, should have been defined before with !!website.define") +// } + +// sort_by := zola.SortBy.from(sort_by_)! +// site_.blog_add( +// name: name +// title: p.get_default('title', '')! +// sort_by: sort_by +// description: description +// )! +// } else if action.name == 'news_add' { +// console.print_debug('website.news_add') +// mut p := action.params +// name := p.get_default('name', '')! +// collection := p.get_default('collection', '')! +// pointer := p.get_default('pointer', '')! +// file := p.get_default('file', '')! +// mut site_ := ws.site or { +// return error("can't find website for news_add, should have been defined before with !!website.define") +// } + +// site_.article_add(name: name, collection: collection, file: file, pointer: pointer)! +// } else if action.name == 'header_add' { +// console.print_debug('website.header_add') +// mut p := action.params +// template := p.get_default('template', '')! +// logo := p.get_default('logo', '')! +// mut site_ := ws.site or { +// return error("can't find website for doctree_add, should have been defined before with !!website.define") +// } + +// site_.header_add(template: template, logo: logo)! +// } else if action.name == 'header_link_add' { +// console.print_debug('website.header_link_add') +// mut p := action.params +// page := p.get_default('page', '')! +// label := p.get_default('label', '')! +// mut site_ := ws.site or { +// return error("can't find website for header_link_add, should have been defined before with !!website.define") +// } + +// site_.header_link_add(page: page, label: label)! +// } else if action.name == 'footer_add' { +// console.print_debug('website.footer_add') +// mut p := action.params +// template := p.get_default('template', '')! +// mut site_ := ws.site or { +// return error("can't find website for doctree_add, should have been defined before with !!website.define") +// } + +// site_.footer_add(template: template)! +// } else if action.name == 'page_add' { +// console.print_debug('website.page_add') +// mut p := action.params +// name := p.get_default('name', '')! +// collection := p.get_default('collection', '')! +// file := p.get_default('file', '')! +// homepage := p.get_default_false('homepage') +// mut site_ := ws.site or { +// return error("can't find website for doctree_add, should have been defined before with !!website.define") +// } + +// site_.page_add(name: name, collection: collection, file: file, homepage: homepage)! + +// // }else if action.name=="pull"{ +// // mut site_:=ws.site or { return error("can't find website for pull, should have been defined before with !!website.define")} +// // site_.pull()! +// } else if action.name == 'section_add' { +// console.print_debug('website.section_add') +// // mut p := action.params +// // name := p.get_default('name', '')! +// // // collection := p.get_default('collection', '')! +// // // file := p.get_default('file', '')! +// // // homepage := p.get_default_false('homepage') +// // mut site_ := ws.site or { +// // return error("can't find website for doctree_add, should have been defined before with !!website.define") +// // } + +// // site_.add_section(name: name)! + +// // }else if action.name=="pull"{ +// // mut site_:=ws.site or { return error("can't find website for pull, should have been defined before with !!website.define")} +// // site_.pull()! +// } else if action.name == 'generate' { +// mut site_ := ws.site or { +// return error("can't find website for generate, should have been defined before with !!website.define") +// } + +// site_.generate()! +// // site_.serve()! +// } else { +// return error("Cannot find right action for website. Found '${action.name}' which is a non understood action for !!website.") +// } +// action.done = true +// } +// } + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/factory.v +```v +module playcmds + +import freeflowuniverse.herolib.core.playbook { PlayBook } +import freeflowuniverse.herolib.data.doctree +import freeflowuniverse.herolib.biz.bizmodel +import freeflowuniverse.herolib.web.site +import freeflowuniverse.herolib.web.docusaurus +import freeflowuniverse.herolib.clients.openai + +// ------------------------------------------------------------------- +// run – entry point for all HeroScript play‑commands +// ------------------------------------------------------------------- + +@[params] +pub struct PlayArgs { +pub mut: + heroscript string + heroscript_path string + plbook ?PlayBook + reset bool +} + +pub fn run(args_ PlayArgs) ! { + mut args := args_ + mut plbook := args.plbook or { + playbook.new(text: args.heroscript, path: args.heroscript_path)! + } + + // Core actions + play_core(mut plbook)! + // Git actions + play_git(mut plbook)! + + // Business model (e.g. currency, bizmodel) + bizmodel.play(mut plbook)! + + // OpenAI client + openai.play(mut plbook)! + + // Website / docs + site.play(mut plbook)! + doctree.play(mut plbook)! + docusaurus.play(mut plbook)! + + // Ensure we did not leave any actions un‑processed + plbook.empty_check()! +} + +``` + +File: /Users/despiegk/code/github/freeflowuniverse/herolib/lib/core/playcmds/play_core.v +```v +module playcmds + +import freeflowuniverse.herolib.develop.gittools +import freeflowuniverse.herolib.core.playbook { PlayBook } +import freeflowuniverse.herolib.ui.console +import freeflowuniverse.herolib.core.texttools + +// ------------------------------------------------------------------- +// Core play‑command processing (context, session, env‑subst, etc) +// ------------------------------------------------------------------- + +fn play_core(mut plbook PlayBook) ! { + // ---------------------------------------------------------------- + // 1. Include handling (play include / echo) + // ---------------------------------------------------------------- + // Track included paths to prevent infinite recursion + mut included_paths := map[string]bool{} + + for action_ in plbook.find(filter: 'play.*')! { + if action_.name == 'include' { + console.print_debug('play run:${action_}') + mut action := *action_ + mut playrunpath := action.params.get_default('path', '')! + if playrunpath.len == 0 { + action.name = 'pull' + playrunpath = gittools.path( + path: action.params.get_default('path', '')! + git_url: action.params.get_default('git_url', '')! + git_reset: action.params.get_default_false('git_reset') + git_pull: action.params.get_default_false('git_pull') + )! + } + if playrunpath.len == 0 { + return error("can't run a heroscript didn't find url or path.") + } + + // Check for cycle detection + if playrunpath in included_paths { + console.print_debug('Skipping already included path: ${playrunpath}') + continue + } + + console.print_debug('play run path:${playrunpath}') + included_paths[playrunpath] = true + plbook.add(path: playrunpath)! + } + if action_.name == 'echo' { + content := action_.params.get_default('content', "didn't find content")! + console.print_header(content) + } + } + + // ---------------------------------------------------------------- + // 2. Session environment handling + // ---------------------------------------------------------------- + // Guard – make sure a session exists + mut session := plbook.session + + // !!session.env_set / env_set_once + for mut action in plbook.find(filter: 'session.')! { + mut p := action.params + match action.name { + 'env_set' { + key := p.get('key')! + val := p.get('val') or { p.get('value')! } + session.env_set(key, val)! + } + 'env_set_once' { + key := p.get('key')! + val := p.get('val') or { p.get('value')! } + // Use the dedicated “set‑once” method + session.env_set_once(key, val)! + } + else { /* ignore unknown sub‑action */ } + } + action.done = true + } + + // ---------------------------------------------------------------- + // 3. Template replacement in action parameters + // ---------------------------------------------------------------- + // Apply template replacement from session environment variables + if session.env.len > 0 { + // Create a map with name_fix applied to keys for template replacement + mut env_fixed := map[string]string{} + for key, value in session.env { + env_fixed[texttools.name_fix(key)] = value + } + + for mut action in plbook.actions { + if !action.done { + action.params.replace(env_fixed) + } + } + } + + for mut action in plbook.find(filter: 'core.coderoot_set')! { + mut p := action.params + if p.exists('coderoot') { + coderoot := p.get_path_create('coderoot')! + if session.context.config.coderoot != coderoot { + session.context.config.coderoot = coderoot + session.context.save()! + } + } else { + return error('coderoot needs to be specified') + } + action.done = true + } + + for mut action in plbook.find(filter: 'core.params_context_set')! { + mut p := action.params + mut context_params := session.context.params()! + for param in p.params { + context_params.set(param.key, param.value) + } + session.context.save()! + action.done = true + } + + for mut action in plbook.find(filter: 'core.params_session_set')! { + mut p := action.params + for param in p.params { + session.params.set(param.key, param.value) + } + session.save()! + action.done = true + } +} + +``` + + +these are my instructions what needs to be done with the attached code + +TODO… + diff --git a/lib/installers/base/redis.v b/lib/installers/base/redis.v index 63b70089..adaf8942 100644 --- a/lib/installers/base/redis.v +++ b/lib/installers/base/redis.v @@ -90,7 +90,7 @@ pub fn start(args RedisInstallArgs) ! { // osal.exec(cmd:"redis-server ${configfilepath()} --daemonize yes")! // } } else { - mut sm := startupmanager.get()! + mut sm := startupmanager.get(.auto)! sm.new(name: 'redis', cmd: 'redis-server ${configfilepath()}', start: true)! } diff --git a/lib/installers/db/cometbft/cometbft_actions.v b/lib/installers/db/cometbft/cometbft_actions.v index 6cf1d112..c391931d 100644 --- a/lib/installers/db/cometbft/cometbft_actions.v +++ b/lib/installers/db/cometbft/cometbft_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'cometbft' // cmd: 'cometbft server' // env: { diff --git a/lib/installers/db/cometbft/cometbft_factory_.v b/lib/installers/db/cometbft/cometbft_factory_.v index 82fd5e02..43dfc0e0 100644 --- a/lib/installers/db/cometbft/cometbft_factory_.v +++ b/lib/installers/db/cometbft/cometbft_factory_.v @@ -3,8 +3,8 @@ module cometbft import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&CometBFT { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&CometBFT { mut obj := CometBFT{ name: args.name } - if args.name !in cometbft_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&CometBFT { + mut context := base.context()! + cometbft_default = args.name + if args.fromdb || args.name !in cometbft_global { + mut r := context.redis()! + if r.hexists('context:cometbft', args.name)! { + data := r.hget('context:cometbft', args.name)! + if data.len == 0 { + return error('CometBFT with name: cometbft does not exist, prob bug.') + } + mut obj := json.decode(CometBFT, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('cometbft', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("CometBFT with name 'cometbft' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return cometbft_global[args.name] or { - println(cometbft_global) - // bug if we get here because should be in globals - panic('could not get config for cometbft with name, is bug:${args.name}') + return error('could not get config for cometbft with name:cometbft') } } // register the config for the future pub fn set(o CometBFT) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + cometbft_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('cometbft', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:cometbft', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('cometbft', args.name) + mut r := context.redis()! + return r.hexists('context:cometbft', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('cometbft', args.name)! - if args.name in cometbft_global { - // del cometbft_global[args.name] + mut r := context.redis()! + r.hdel('context:cometbft', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&CometBFT { + mut res := []&CometBFT{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + cometbft_global = map[string]&CometBFT{} + cometbft_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:cometbft')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in cometbft_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o CometBFT) ! { +fn set_in_mem(o CometBFT) !CometBFT { mut o2 := obj_init(o)! - cometbft_global[o.name] = &o2 - cometbft_default = o.name + cometbft_global[o2.name] = &o2 + cometbft_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'cometbft.') { + return + } mut install_actions := plbook.find(filter: 'cometbft.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'cometbft.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,24 +170,28 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } @@ -223,10 +266,12 @@ pub fn (mut self CometBFT) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -260,10 +305,3 @@ pub fn (mut self CometBFT) destroy() ! { pub fn switch(name string) { cometbft_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/db/meilisearch_installer/meilisearch_installer_actions.v b/lib/installers/db/meilisearch_installer/meilisearch_installer_actions.v index 6bdb3ddc..3b93f936 100644 --- a/lib/installers/db/meilisearch_installer/meilisearch_installer_actions.v +++ b/lib/installers/db/meilisearch_installer/meilisearch_installer_actions.v @@ -2,7 +2,7 @@ module meilisearch_installer import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.core.httpconnection import freeflowuniverse.herolib.core.texttools @@ -22,14 +22,14 @@ fn generate_master_key(length int) !string { return key.string() } -fn startupcmd() ![]zinit.ZProcessNewArgs { - mut res := []zinit.ZProcessNewArgs{} +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} mut installer := get()! mut env := 'development' if installer.production { env = 'production' } - res << zinit.ZProcessNewArgs{ + res << startupmanager.ZProcessNewArgs{ name: 'meilisearch' cmd: 'meilisearch --no-analytics --http-addr ${installer.host}:${installer.port} --env ${env} --db-path ${installer.path} --master-key ${installer.masterkey}' startuptype: .zinit diff --git a/lib/installers/db/meilisearch_installer/meilisearch_installer_factory_.v b/lib/installers/db/meilisearch_installer/meilisearch_installer_factory_.v index 79ed36c2..a0f1ebdf 100644 --- a/lib/installers/db/meilisearch_installer/meilisearch_installer_factory_.v +++ b/lib/installers/db/meilisearch_installer/meilisearch_installer_factory_.v @@ -3,8 +3,8 @@ module meilisearch_installer import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&MeilisearchInstaller { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&MeilisearchInstaller { mut obj := MeilisearchInstaller{ name: args.name } - if args.name !in meilisearch_installer_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&MeilisearchInstaller { + mut context := base.context()! + meilisearch_installer_default = args.name + if args.fromdb || args.name !in meilisearch_installer_global { + mut r := context.redis()! + if r.hexists('context:meilisearch_installer', args.name)! { + data := r.hget('context:meilisearch_installer', args.name)! + if data.len == 0 { + return error('MeilisearchInstaller with name: meilisearch_installer does not exist, prob bug.') + } + mut obj := json.decode(MeilisearchInstaller, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('meilisearch_installer', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("MeilisearchInstaller with name 'meilisearch_installer' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return meilisearch_installer_global[args.name] or { - println(meilisearch_installer_global) - // bug if we get here because should be in globals - panic('could not get config for meilisearch_installer with name, is bug:${args.name}') + return error('could not get config for meilisearch_installer with name:meilisearch_installer') } } // register the config for the future pub fn set(o MeilisearchInstaller) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + meilisearch_installer_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('meilisearch_installer', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:meilisearch_installer', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('meilisearch_installer', args.name) + mut r := context.redis()! + return r.hexists('context:meilisearch_installer', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('meilisearch_installer', args.name)! - if args.name in meilisearch_installer_global { - // del meilisearch_installer_global[args.name] + mut r := context.redis()! + r.hdel('context:meilisearch_installer', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&MeilisearchInstaller { + mut res := []&MeilisearchInstaller{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + meilisearch_installer_global = map[string]&MeilisearchInstaller{} + meilisearch_installer_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:meilisearch_installer')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in meilisearch_installer_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o MeilisearchInstaller) ! { +fn set_in_mem(o MeilisearchInstaller) !MeilisearchInstaller { mut o2 := obj_init(o)! - meilisearch_installer_global[o.name] = &o2 - meilisearch_installer_default = o.name + meilisearch_installer_global[o2.name] = &o2 + meilisearch_installer_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'meilisearch_installer.') { + return + } mut install_actions := plbook.find(filter: 'meilisearch_installer.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'meilisearch_installer.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,24 +170,28 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } @@ -223,10 +266,12 @@ pub fn (mut self MeilisearchInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -260,10 +305,3 @@ pub fn (mut self MeilisearchInstaller) destroy() ! { pub fn switch(name string) { meilisearch_installer_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/db/postgresql/postgresql_actions.v b/lib/installers/db/postgresql/postgresql_actions.v index c8102d1d..6feec92a 100644 --- a/lib/installers/db/postgresql/postgresql_actions.v +++ b/lib/installers/db/postgresql/postgresql_actions.v @@ -3,23 +3,24 @@ module postgresql import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.installers.virt.podman as podman_installer -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut cfg := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} cmd := " mkdir -p ${cfg.volume_path} podman run --name ${cfg.container_name} -e POSTGRES_USER=${cfg.user} -e POSTGRES_PASSWORD=\"${cfg.password}\" -v ${cfg.volume_path}:/var/lib/postgresql/data -p ${cfg.port}:5432 --health-cmd=\"pg_isready -U ${cfg.user}\" postgres:latest " - res << zinit.ZProcessNewArgs{ + res << startupmanager.ZProcessNewArgs{ name: 'postgresql' cmd: cmd startuptype: .zinit } + return res } diff --git a/lib/installers/db/postgresql/postgresql_factory_.v b/lib/installers/db/postgresql/postgresql_factory_.v index 54491266..e4bd6307 100644 --- a/lib/installers/db/postgresql/postgresql_factory_.v +++ b/lib/installers/db/postgresql/postgresql_factory_.v @@ -3,8 +3,8 @@ module postgresql import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&Postgresql { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&Postgresql { mut obj := Postgresql{ name: args.name } - if args.name !in postgresql_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&Postgresql { + mut context := base.context()! + postgresql_default = args.name + if args.fromdb || args.name !in postgresql_global { + mut r := context.redis()! + if r.hexists('context:postgresql', args.name)! { + data := r.hget('context:postgresql', args.name)! + if data.len == 0 { + return error('Postgresql with name: postgresql does not exist, prob bug.') + } + mut obj := json.decode(Postgresql, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('postgresql', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("Postgresql with name 'postgresql' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return postgresql_global[args.name] or { - println(postgresql_global) - // bug if we get here because should be in globals - panic('could not get config for postgresql with name, is bug:${args.name}') + return error('could not get config for postgresql with name:postgresql') } } // register the config for the future pub fn set(o Postgresql) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + postgresql_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('postgresql', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:postgresql', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('postgresql', args.name) + mut r := context.redis()! + return r.hexists('context:postgresql', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('postgresql', args.name)! - if args.name in postgresql_global { - // del postgresql_global[args.name] + mut r := context.redis()! + r.hdel('context:postgresql', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&Postgresql { + mut res := []&Postgresql{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + postgresql_global = map[string]&Postgresql{} + postgresql_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:postgresql')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in postgresql_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o Postgresql) ! { +fn set_in_mem(o Postgresql) !Postgresql { mut o2 := obj_init(o)! - postgresql_global[o.name] = &o2 - postgresql_default = o.name + postgresql_global[o2.name] = &o2 + postgresql_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'postgresql.') { + return + } mut install_actions := plbook.find(filter: 'postgresql.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'postgresql.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,36 +170,38 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } // load from disk and make sure is properly intialized pub fn (mut self Postgresql) reload() ! { - switch(self.name) self = obj_init(self)! } pub fn (mut self Postgresql) start() ! { - switch(self.name) if self.running()! { return } @@ -223,10 +264,12 @@ pub fn (mut self Postgresql) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -253,12 +296,4 @@ pub fn (mut self Postgresql) destroy() ! { // switch instance to be used for postgresql pub fn switch(name string) { - postgresql_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/db/qdrant_installer/qdrant_installer_actions.v b/lib/installers/db/qdrant_installer/qdrant_installer_actions.v index c2d767a8..76579766 100644 --- a/lib/installers/db/qdrant_installer/qdrant_installer_actions.v +++ b/lib/installers/db/qdrant_installer/qdrant_installer_actions.v @@ -1,17 +1,18 @@ module qdrant_installer import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import os -fn startupcmd() ![]zinit.ZProcessNewArgs { - mut res := []zinit.ZProcessNewArgs{} - res << zinit.ZProcessNewArgs{ +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} + res << startupmanager.ZProcessNewArgs{ name: 'qdrant' cmd: 'sleep 5 && qdrant --config-path ${os.home_dir()}/hero/var/qdrant/config.yaml' startuptype: .zinit } + return res } diff --git a/lib/installers/db/qdrant_installer/qdrant_installer_factory_.v b/lib/installers/db/qdrant_installer/qdrant_installer_factory_.v index bfda48a8..37e6f02e 100644 --- a/lib/installers/db/qdrant_installer/qdrant_installer_factory_.v +++ b/lib/installers/db/qdrant_installer/qdrant_installer_factory_.v @@ -3,8 +3,8 @@ module qdrant_installer import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&QDrant { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&QDrant { mut obj := QDrant{ name: args.name } - if args.name !in qdrant_installer_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&QDrant { + mut context := base.context()! + qdrant_installer_default = args.name + if args.fromdb || args.name !in qdrant_installer_global { + mut r := context.redis()! + if r.hexists('context:qdrant_installer', args.name)! { + data := r.hget('context:qdrant_installer', args.name)! + if data.len == 0 { + return error('QDrant with name: qdrant_installer does not exist, prob bug.') + } + mut obj := json.decode(QDrant, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('qdrant_installer', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("QDrant with name 'qdrant_installer' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return qdrant_installer_global[args.name] or { - println(qdrant_installer_global) - // bug if we get here because should be in globals - panic('could not get config for qdrant_installer with name, is bug:${args.name}') + return error('could not get config for qdrant_installer with name:qdrant_installer') } } // register the config for the future pub fn set(o QDrant) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + qdrant_installer_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('qdrant_installer', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:qdrant_installer', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('qdrant_installer', args.name) + mut r := context.redis()! + return r.hexists('context:qdrant_installer', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('qdrant_installer', args.name)! - if args.name in qdrant_installer_global { - // del qdrant_installer_global[args.name] + mut r := context.redis()! + r.hdel('context:qdrant_installer', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&QDrant { + mut res := []&QDrant{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + qdrant_installer_global = map[string]&QDrant{} + qdrant_installer_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:qdrant_installer')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in qdrant_installer_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o QDrant) ! { +fn set_in_mem(o QDrant) !QDrant { mut o2 := obj_init(o)! - qdrant_installer_global[o.name] = &o2 - qdrant_installer_default = o.name + qdrant_installer_global[o2.name] = &o2 + qdrant_installer_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'qdrant_installer.') { + return + } mut install_actions := plbook.find(filter: 'qdrant_installer.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'qdrant_installer.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,24 +170,28 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } @@ -223,10 +266,12 @@ pub fn (mut self QDrant) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -260,10 +305,3 @@ pub fn (mut self QDrant) destroy() ! { pub fn switch(name string) { qdrant_installer_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/db/zerodb/zerodb_actions.v b/lib/installers/db/zerodb/zerodb_actions.v index 226e9270..ba434822 100644 --- a/lib/installers/db/zerodb/zerodb_actions.v +++ b/lib/installers/db/zerodb/zerodb_actions.v @@ -3,7 +3,7 @@ module zerodb import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.develop.gittools import freeflowuniverse.herolib.installers.base @@ -14,19 +14,20 @@ import rand import os import time -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut cfg := get()! mut cmd := 'zdb --socket ${os.home_dir()}/var/zdb.sock --port ${cfg.port} --admin ${cfg.secret} --data ${cfg.datadir} --index ${cfg.indexdir} --dualnet --protect --rotate ${cfg.rotateperiod}' if cfg.sequential { cmd += ' --mode seq' } - mut res := []zinit.ZProcessNewArgs{} - res << zinit.ZProcessNewArgs{ + mut res := []startupmanager.ZProcessNewArgs{} + res << startupmanager.ZProcessNewArgs{ name: 'zdb' cmd: cmd startuptype: .zinit } + return res } diff --git a/lib/installers/db/zerodb/zerodb_factory_.v b/lib/installers/db/zerodb/zerodb_factory_.v index a7f274d0..46398d1a 100644 --- a/lib/installers/db/zerodb/zerodb_factory_.v +++ b/lib/installers/db/zerodb/zerodb_factory_.v @@ -3,8 +3,8 @@ module zerodb import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&ZeroDB { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&ZeroDB { mut obj := ZeroDB{ name: args.name } - if args.name !in zerodb_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&ZeroDB { + mut context := base.context()! + zerodb_default = args.name + if args.fromdb || args.name !in zerodb_global { + mut r := context.redis()! + if r.hexists('context:zerodb', args.name)! { + data := r.hget('context:zerodb', args.name)! + if data.len == 0 { + return error('ZeroDB with name: zerodb does not exist, prob bug.') + } + mut obj := json.decode(ZeroDB, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('zerodb', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("ZeroDB with name 'zerodb' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return zerodb_global[args.name] or { - println(zerodb_global) - // bug if we get here because should be in globals - panic('could not get config for zerodb with name, is bug:${args.name}') + return error('could not get config for zerodb with name:zerodb') } } // register the config for the future pub fn set(o ZeroDB) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + zerodb_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('zerodb', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:zerodb', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('zerodb', args.name) + mut r := context.redis()! + return r.hexists('context:zerodb', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('zerodb', args.name)! - if args.name in zerodb_global { - // del zerodb_global[args.name] + mut r := context.redis()! + r.hdel('context:zerodb', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&ZeroDB { + mut res := []&ZeroDB{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + zerodb_global = map[string]&ZeroDB{} + zerodb_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:zerodb')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in zerodb_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o ZeroDB) ! { +fn set_in_mem(o ZeroDB) !ZeroDB { mut o2 := obj_init(o)! - zerodb_global[o.name] = &o2 - zerodb_default = o.name + zerodb_global[o2.name] = &o2 + zerodb_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'zerodb.') { + return + } mut install_actions := plbook.find(filter: 'zerodb.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'zerodb.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,36 +170,38 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } // load from disk and make sure is properly intialized pub fn (mut self ZeroDB) reload() ! { - switch(self.name) self = obj_init(self)! } pub fn (mut self ZeroDB) start() ! { - switch(self.name) if self.running()! { return } @@ -223,10 +264,12 @@ pub fn (mut self ZeroDB) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -258,12 +301,4 @@ pub fn (mut self ZeroDB) destroy() ! { // switch instance to be used for zerodb pub fn switch(name string) { - zerodb_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/db/zerofs/zerofs_actions.v b/lib/installers/db/zerofs/zerofs_actions.v index c36514ef..8284594e 100644 --- a/lib/installers/db/zerofs/zerofs_actions.v +++ b/lib/installers/db/zerofs/zerofs_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'zerofs' // cmd: 'zerofs server' // env: { diff --git a/lib/installers/db/zerofs/zerofs_factory_.v b/lib/installers/db/zerofs/zerofs_factory_.v index 82b05801..3a114bb6 100644 --- a/lib/installers/db/zerofs/zerofs_factory_.v +++ b/lib/installers/db/zerofs/zerofs_factory_.v @@ -2,8 +2,8 @@ module zerofs import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&ZeroFS { +pub fn new(args ArgsGet) !&ZeroFS { return &ZeroFS{} } +pub fn get(args ArgsGet) !&ZeroFS { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'zerofs.') { + return + } + mut install_actions := plbook.find(filter: 'zerofs.configure')! + if install_actions.len > 0 { + return error("can't configure zerofs, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'zerofs.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self ZeroFS) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self ZeroFS) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self ZeroFS) destroy() ! { // switch instance to be used for zerofs pub fn switch(name string) { - zerofs_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/infra/coredns/coredns_actions.v b/lib/installers/infra/coredns/coredns_actions.v index d77fc457..494150ac 100644 --- a/lib/installers/infra/coredns/coredns_actions.v +++ b/lib/installers/infra/coredns/coredns_actions.v @@ -6,20 +6,21 @@ import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.core import freeflowuniverse.herolib.develop.gittools -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.core.httpconnection import freeflowuniverse.herolib.installers.lang.golang import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut args := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} cmd := "coredns -conf '${args.config_path}'" - res << zinit.ZProcessNewArgs{ + res << startupmanager.ZProcessNewArgs{ name: 'coredns' cmd: cmd } + return res } diff --git a/lib/installers/infra/coredns/coredns_configure.v b/lib/installers/infra/coredns/coredns_configure.v index c45fe364..1e358457 100644 --- a/lib/installers/infra/coredns/coredns_configure.v +++ b/lib/installers/infra/coredns/coredns_configure.v @@ -7,7 +7,7 @@ import os pub fn configure() ! { mut args := get()! - mut gs := gittools.get()! + mut gs := gittools.new()! mut repo_path := '' set_global_dns() diff --git a/lib/installers/infra/coredns/coredns_factory_.v b/lib/installers/infra/coredns/coredns_factory_.v index b3ebee54..515f6026 100644 --- a/lib/installers/infra/coredns/coredns_factory_.v +++ b/lib/installers/infra/coredns/coredns_factory_.v @@ -3,8 +3,8 @@ module coredns import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&CoreDNS { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&CoreDNS { mut obj := CoreDNS{ name: args.name } - if args.name !in coredns_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&CoreDNS { + mut context := base.context()! + coredns_default = args.name + if args.fromdb || args.name !in coredns_global { + mut r := context.redis()! + if r.hexists('context:coredns', args.name)! { + data := r.hget('context:coredns', args.name)! + if data.len == 0 { + return error('CoreDNS with name: coredns does not exist, prob bug.') + } + mut obj := json.decode(CoreDNS, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('coredns', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("CoreDNS with name 'coredns' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return coredns_global[args.name] or { - println(coredns_global) - // bug if we get here because should be in globals - panic('could not get config for coredns with name, is bug:${args.name}') + return error('could not get config for coredns with name:coredns') } } // register the config for the future pub fn set(o CoreDNS) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + coredns_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('coredns', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:coredns', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('coredns', args.name) + mut r := context.redis()! + return r.hexists('context:coredns', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('coredns', args.name)! - if args.name in coredns_global { - // del coredns_global[args.name] + mut r := context.redis()! + r.hdel('context:coredns', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&CoreDNS { + mut res := []&CoreDNS{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + coredns_global = map[string]&CoreDNS{} + coredns_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:coredns')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in coredns_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o CoreDNS) ! { +fn set_in_mem(o CoreDNS) !CoreDNS { mut o2 := obj_init(o)! - coredns_global[o.name] = &o2 - coredns_default = o.name + coredns_global[o2.name] = &o2 + coredns_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'coredns.') { + return + } mut install_actions := plbook.find(filter: 'coredns.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'coredns.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,36 +170,38 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } // load from disk and make sure is properly intialized pub fn (mut self CoreDNS) reload() ! { - switch(self.name) self = obj_init(self)! } pub fn (mut self CoreDNS) start() ! { - switch(self.name) if self.running()! { return } @@ -223,10 +264,12 @@ pub fn (mut self CoreDNS) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -258,12 +301,4 @@ pub fn (mut self CoreDNS) destroy() ! { // switch instance to be used for coredns pub fn switch(name string) { - coredns_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/infra/gitea/gitea_actions.v b/lib/installers/infra/gitea/gitea_actions.v index ca9b4b7e..4ea704bf 100644 --- a/lib/installers/infra/gitea/gitea_actions.v +++ b/lib/installers/infra/gitea/gitea_actions.v @@ -5,7 +5,7 @@ import freeflowuniverse.herolib.core import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.installers.ulist -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import os fn installed() !bool { @@ -30,16 +30,12 @@ fn install() ! { mut url := '' if core.is_linux_arm()! { - // https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-linux-arm64.xz url = '${baseurl}-linux-arm64.xz' } else if core.is_linux_intel()! { - // https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-linux-amd64.xz url = '${baseurl}-linux-amd64.xz' } else if core.is_osx_arm()! { - // https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-darwin-10.12-arm64.xz url = '${baseurl}-darwin-10.12-arm64.xz' } else if core.is_osx_intel()! { - // https://github.com/go-gitea/gitea/releases/download/v1.23.2/gitea-1.23.2-darwin-10.12-amd64.xz url = '${baseurl}-darwin-10.12-amd64.xz' } else { return error('unsported platform') @@ -90,10 +86,10 @@ fn ulist_get() !ulist.UList { // uploads to S3 server if configured fn upload() ! {} -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut cfg := get()! - mut res := []zinit.ZProcessNewArgs{} - res << zinit.ZProcessNewArgs{ + mut res := []startupmanager.ZProcessNewArgs{} + res << startupmanager.ZProcessNewArgs{ name: 'gitea' cmd: 'gitea server' env: { @@ -101,11 +97,12 @@ fn startupcmd() ![]zinit.ZProcessNewArgs { 'GITEA_CONFIG': cfg.config_path() } } + return res - // mut res := []zinit.ZProcessNewArgs{} + // mut res := []startupmanager.ZProcessNewArgs{} // cfg := get()! - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'gitea' // // cmd: 'GITEA_WORK_DIR=${cfg.path} sudo -u git /var/lib/git/gitea web -c /etc/gitea_app.ini' // cmd: ' @@ -151,7 +148,7 @@ fn startupcmd() ![]zinit.ZProcessNewArgs { // ' // workdir: cfg.path // } - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'restart_gitea' // cmd: 'sleep 30 && zinit restart gitea && exit 1' // after: ['gitea'] diff --git a/lib/installers/infra/gitea/gitea_factory_.v b/lib/installers/infra/gitea/gitea_factory_.v index 0c4846b7..80153129 100644 --- a/lib/installers/infra/gitea/gitea_factory_.v +++ b/lib/installers/infra/gitea/gitea_factory_.v @@ -3,8 +3,8 @@ module gitea import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&GiteaServer { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&GiteaServer { mut obj := GiteaServer{ name: args.name } - if args.name !in gitea_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&GiteaServer { + mut context := base.context()! + gitea_default = args.name + if args.fromdb || args.name !in gitea_global { + mut r := context.redis()! + if r.hexists('context:gitea', args.name)! { + data := r.hget('context:gitea', args.name)! + if data.len == 0 { + return error('GiteaServer with name: gitea does not exist, prob bug.') + } + mut obj := json.decode(GiteaServer, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('gitea', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("GiteaServer with name 'gitea' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return gitea_global[args.name] or { - println(gitea_global) - // bug if we get here because should be in globals - panic('could not get config for gitea with name, is bug:${args.name}') + return error('could not get config for gitea with name:gitea') } } // register the config for the future pub fn set(o GiteaServer) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + gitea_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('gitea', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:gitea', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('gitea', args.name) + mut r := context.redis()! + return r.hexists('context:gitea', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('gitea', args.name)! - if args.name in gitea_global { - // del gitea_global[args.name] + mut r := context.redis()! + r.hdel('context:gitea', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&GiteaServer { + mut res := []&GiteaServer{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + gitea_global = map[string]&GiteaServer{} + gitea_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:gitea')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in gitea_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o GiteaServer) ! { +fn set_in_mem(o GiteaServer) !GiteaServer { mut o2 := obj_init(o)! - gitea_global[o.name] = &o2 - gitea_default = o.name + gitea_global[o2.name] = &o2 + gitea_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'gitea.') { + return + } mut install_actions := plbook.find(filter: 'gitea.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'gitea.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,36 +170,38 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } // load from disk and make sure is properly intialized pub fn (mut self GiteaServer) reload() ! { - switch(self.name) self = obj_init(self)! } pub fn (mut self GiteaServer) start() ! { - switch(self.name) if self.running()! { return } @@ -223,10 +264,12 @@ pub fn (mut self GiteaServer) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -258,12 +301,4 @@ pub fn (mut self GiteaServer) destroy() ! { // switch instance to be used for gitea pub fn switch(name string) { - gitea_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/infra/gitea/gitea_model.v b/lib/installers/infra/gitea/gitea_model.v index 1ad2b4bd..c30c8251 100644 --- a/lib/installers/infra/gitea/gitea_model.v +++ b/lib/installers/infra/gitea/gitea_model.v @@ -10,7 +10,7 @@ import freeflowuniverse.herolib.clients.mailclient import freeflowuniverse.herolib.clients.postgresql_client import rand -pub const version = '1.23.3' +pub const version = '1.24.5' const singleton = true const default = false diff --git a/lib/installers/infra/livekit/livekit_actions.v b/lib/installers/infra/livekit/livekit_actions.v index 408aab56..50aea11a 100644 --- a/lib/installers/infra/livekit/livekit_actions.v +++ b/lib/installers/infra/livekit/livekit_actions.v @@ -1,7 +1,7 @@ module livekit import freeflowuniverse.herolib.osal.core as osal -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.installers.ulist @@ -45,10 +45,10 @@ fn generate_keys() ! { obj.apisecret = api_secret } -fn startupcmd() ![]zinit.ZProcessNewArgs { - mut res := []zinit.ZProcessNewArgs{} +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} mut installer := get()! - res << zinit.ZProcessNewArgs{ + res << startupmanager.ZProcessNewArgs{ name: 'livekit' cmd: 'livekit-server --config ${installer.configpath} --bind 0.0.0.0' startuptype: .zinit diff --git a/lib/installers/infra/livekit/livekit_factory_.v b/lib/installers/infra/livekit/livekit_factory_.v index 906b3d88..7b617c0e 100644 --- a/lib/installers/infra/livekit/livekit_factory_.v +++ b/lib/installers/infra/livekit/livekit_factory_.v @@ -3,8 +3,8 @@ module livekit import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&LivekitServer { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&LivekitServer { mut obj := LivekitServer{ name: args.name } - if args.name !in livekit_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&LivekitServer { + mut context := base.context()! + livekit_default = args.name + if args.fromdb || args.name !in livekit_global { + mut r := context.redis()! + if r.hexists('context:livekit', args.name)! { + data := r.hget('context:livekit', args.name)! + if data.len == 0 { + return error('LivekitServer with name: livekit does not exist, prob bug.') + } + mut obj := json.decode(LivekitServer, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('livekit', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("LivekitServer with name 'livekit' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return livekit_global[args.name] or { - println(livekit_global) - // bug if we get here because should be in globals - panic('could not get config for livekit with name, is bug:${args.name}') + return error('could not get config for livekit with name:livekit') } } // register the config for the future pub fn set(o LivekitServer) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + livekit_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('livekit', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:livekit', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('livekit', args.name) + mut r := context.redis()! + return r.hexists('context:livekit', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('livekit', args.name)! - if args.name in livekit_global { - // del livekit_global[args.name] + mut r := context.redis()! + r.hdel('context:livekit', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&LivekitServer { + mut res := []&LivekitServer{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + livekit_global = map[string]&LivekitServer{} + livekit_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:livekit')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in livekit_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o LivekitServer) ! { +fn set_in_mem(o LivekitServer) !LivekitServer { mut o2 := obj_init(o)! - livekit_global[o.name] = &o2 - livekit_default = o.name + livekit_global[o2.name] = &o2 + livekit_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'livekit.') { + return + } mut install_actions := plbook.find(filter: 'livekit.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'livekit.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,24 +170,28 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } @@ -223,10 +266,12 @@ pub fn (mut self LivekitServer) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -255,10 +300,3 @@ pub fn (mut self LivekitServer) destroy() ! { pub fn switch(name string) { livekit_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/infra/screen/screen_factory_.v b/lib/installers/infra/screen/screen_factory_.v index 074f110f..97620f39 100644 --- a/lib/installers/infra/screen/screen_factory_.v +++ b/lib/installers/infra/screen/screen_factory_.v @@ -2,8 +2,8 @@ module screen import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( screen_global map[string]&Screen @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Screen { +pub fn new(args ArgsGet) !&Screen { return &Screen{} } +pub fn get(args ArgsGet) !&Screen { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'screen.') { + return + } + mut install_actions := plbook.find(filter: 'screen.configure')! + if install_actions.len > 0 { + return error("can't configure screen, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'screen.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -88,10 +77,3 @@ pub fn (mut self Screen) destroy() ! { pub fn switch(name string) { screen_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/infra/zinit_installer/zinit_installer_actions.v b/lib/installers/infra/zinit_installer/zinit_installer_actions.v index e6e5a28b..12e4c7ab 100644 --- a/lib/installers/infra/zinit_installer/zinit_installer_actions.v +++ b/lib/installers/infra/zinit_installer/zinit_installer_actions.v @@ -6,18 +6,28 @@ import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.develop.gittools import freeflowuniverse.herolib.core import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit as zinit_module import freeflowuniverse.herolib.installers.ulist +import freeflowuniverse.herolib.installers.lang.rust +import freeflowuniverse.herolib.osal.startupmanager import os -fn startupcmd() ![]zinit_module.ZProcessNewArgs { - mut res := []zinit_module.ZProcessNewArgs{} - res << zinit_module.ZProcessNewArgs{ - name: 'zinit' - cmd: '/usr/local/bin/zinit init' - startuptype: .systemd - start: true - restart: true +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} + if core.is_linux()! { + res << startupmanager.ZProcessNewArgs{ + name: 'zinit' + cmd: '/usr/local/bin/zinit init ~/hero/cfg/zinit' + startuptype: .systemd + start: true + restart: true + } + } else { + res << startupmanager.ZProcessNewArgs{ + name: 'zinit' + cmd: '~/hero/bin/zinit init --config ~/hero/cfg/zinit' + startuptype: .screen + start: true + } } return res } @@ -70,25 +80,29 @@ fn upload() ! {} fn install() ! { console.print_header('install zinit') - if !core.is_linux()! { - return error('only support linux for now') + baseurl := 'https://github.com/threefoldtech/zinit/releases/download/v${version}/zinit-' + + mut url := '' + + if core.is_linux_intel()! { + url = '${baseurl}linux-x86_64' + } else if core.is_osx_arm()! { + url = '${baseurl}macos-aarch64' + } else if core.is_osx_intel()! { + url = '${baseurl}macos-x86_64' + } else { + return error('unsupported platform to install zinit') } - release_url := 'https://github.com/threefoldtech/zinit/releases/download/v0.2.25/zinit' - mut dest := osal.download( - url: release_url - minsize_kb: 2000 - reset: true + url: url + minsize_kb: 4000 )! osal.cmd_add( cmdname: 'zinit' source: dest.path )! - - osal.dir_ensure('/etc/zinit')! - console.print_header('install zinit done') } fn build() ! { @@ -96,12 +110,13 @@ fn build() ! { return error('only support linux for now') } - // rust.install() + mut i := rust.new()! + i.install()! // install zinit if it was already done will return true console.print_header('build zinit') - mut gs := gittools.get(coderoot: '/tmp/builder')! + mut gs := gittools.new(coderoot: '/tmp/builder')! mut repo := gs.get_repo( url: 'https://github.com/threefoldtech/zinit' reset: true @@ -125,8 +140,12 @@ fn build() ! { } fn destroy() ! { - mut systemdfactory := systemd.new()! - systemdfactory.destroy('zinit') or { return error('Could not destroy zinit due to: ${err}') } + if core.is_linux()! { + mut systemdfactory := systemd.new()! + systemdfactory.destroy('zinit') or { + return error('Could not destroy zinit due to: ${err}') + } + } osal.process_kill_recursive(name: 'zinit') or { return error('Could not kill zinit due to: ${err}') diff --git a/lib/installers/infra/zinit_installer/zinit_installer_factory_.v b/lib/installers/infra/zinit_installer/zinit_installer_factory_.v index dfb22f93..eb059b23 100644 --- a/lib/installers/infra/zinit_installer/zinit_installer_factory_.v +++ b/lib/installers/infra/zinit_installer/zinit_installer_factory_.v @@ -2,8 +2,8 @@ module zinit_installer import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&ZinitInstaller { +pub fn new(args ArgsGet) !&ZinitInstaller { return &ZinitInstaller{} } +pub fn get(args ArgsGet) !&ZinitInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'zinit_installer.') { + return + } + mut install_actions := plbook.find(filter: 'zinit_installer.configure')! + if install_actions.len > 0 { + return error("can't configure zinit_installer, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'zinit_installer.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self ZinitInstaller) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self ZinitInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self ZinitInstaller) destroy() ! { // switch instance to be used for zinit_installer pub fn switch(name string) { - zinit_installer_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/infra/zinit_installer/zinit_installer_model.v b/lib/installers/infra/zinit_installer/zinit_installer_model.v index 458d0bc9..5e0f252b 100644 --- a/lib/installers/infra/zinit_installer/zinit_installer_model.v +++ b/lib/installers/infra/zinit_installer/zinit_installer_model.v @@ -2,7 +2,7 @@ module zinit_installer import freeflowuniverse.herolib.data.encoderhero -pub const version = '0.0.0' +pub const version = '0.2.25' const singleton = true const default = true @@ -23,14 +23,3 @@ fn obj_init(mycfg_ ZinitInstaller) !ZinitInstaller { fn configure() ! { // mut installer := get()! } - -/////////////NORMALLY NO NEED TO TOUCH - -pub fn heroscript_dumps(obj ZinitInstaller) !string { - return encoderhero.encode[ZinitInstaller](obj)! -} - -pub fn heroscript_loads(heroscript string) !ZinitInstaller { - mut obj := encoderhero.decode[ZinitInstaller](heroscript)! - return obj -} diff --git a/lib/installers/lang/golang/golang_factory_.v b/lib/installers/lang/golang/golang_factory_.v index 023a4b89..316daa27 100644 --- a/lib/installers/lang/golang/golang_factory_.v +++ b/lib/installers/lang/golang/golang_factory_.v @@ -2,8 +2,8 @@ module golang import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( golang_global map[string]&GolangInstaller @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&GolangInstaller { +pub fn new(args ArgsGet) !&GolangInstaller { return &GolangInstaller{} } +pub fn get(args ArgsGet) !&GolangInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'golang.') { + return + } + mut install_actions := plbook.find(filter: 'golang.configure')! + if install_actions.len > 0 { + return error("can't configure golang, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'golang.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -91,12 +80,4 @@ pub fn (mut self GolangInstaller) destroy() ! { // switch instance to be used for golang pub fn switch(name string) { - golang_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/lang/herolib/crystallib.v b/lib/installers/lang/herolib/crystallib.v index f8adcfac..9a569552 100644 --- a/lib/installers/lang/herolib/crystallib.v +++ b/lib/installers/lang/herolib/crystallib.v @@ -29,7 +29,7 @@ pub fn install(args InstallArgs) ! { vlang.install(reset: args.reset)! vlang.v_analyzer_install(reset: args.reset)! - mut gs := gittools.get()! + mut gs := gittools.new()! gs.config()!.light = true // means we clone depth 1 mut repo := gs.get_repo( diff --git a/lib/installers/lang/nodejs/nodejs_factory_.v b/lib/installers/lang/nodejs/nodejs_factory_.v index 09f9508c..02f69b5d 100644 --- a/lib/installers/lang/nodejs/nodejs_factory_.v +++ b/lib/installers/lang/nodejs/nodejs_factory_.v @@ -2,8 +2,8 @@ module nodejs import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( nodejs_global map[string]&NodeJS @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&NodeJS { +pub fn new(args ArgsGet) !&NodeJS { return &NodeJS{} } +pub fn get(args ArgsGet) !&NodeJS { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'nodejs.') { + return + } + mut install_actions := plbook.find(filter: 'nodejs.configure')! + if install_actions.len > 0 { + return error("can't configure nodejs, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'nodejs.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -86,12 +75,4 @@ pub fn (mut self NodeJS) destroy() ! { // switch instance to be used for nodejs pub fn switch(name string) { - nodejs_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/lang/python/.heroscript b/lib/installers/lang/python/.heroscript index 2e712c7a..30763937 100644 --- a/lib/installers/lang/python/.heroscript +++ b/lib/installers/lang/python/.heroscript @@ -1,12 +1,13 @@ -!!hero_code.generate_installer - name: "python" - classname: "Python" - hasconfig: false - singleton: true - default: true - title: "" - templates: false - build: false - startupmanager: false - supported_platforms: "" +!!hero_code.generate_installer + name:'python' + classname:'PythonInstaller' + singleton:1 + templates:0 + default:1 + title:'' + supported_platforms:'' + reset:0 + startupmanager:0 + hasconfig:0 + build:0 \ No newline at end of file diff --git a/lib/installers/lang/python/python_actions.v b/lib/installers/lang/python/python_actions.v index b7d1e7f7..c0834948 100644 --- a/lib/installers/lang/python/python_actions.v +++ b/lib/installers/lang/python/python_actions.v @@ -1,31 +1,28 @@ module python import freeflowuniverse.herolib.osal.core as osal -import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core import freeflowuniverse.herolib.installers.base -import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.installers.ulist +import freeflowuniverse.herolib.core.texttools import os //////////////////// following actions are not specific to instance of the object -// checks if a certain version or above is installed fn installed() !bool { - res := os.execute('python3 --version') + res := os.execute('${osal.profile_path_source_and()!} uv self version') if res.exit_code != 0 { return false } - r := res.output.split_into_lines().filter(it.trim_space().len > 0) if r.len != 1 { - return error("couldn't parse pnpm version.\n${res.output}") + return error("couldn't parse python version.\n${res.output}") } - - if texttools.version(r[0].all_after_first('ython')) >= texttools.version(version) { + r2 := r[0].split(' ')[1] or { return error("couldn't parse python version.\n${res.output}") } + if texttools.version(version) <= texttools.version(r2) { return true } - return false } @@ -49,29 +46,44 @@ fn install() ! { osal.package_install('python3')! pl := core.platform()! - if pl == .arch { - osal.package_install('python-pipx,sqlite')! - } else if pl == .ubuntu { - osal.package_install('pipx,sqlite')! + if pl == .ubuntu { + osal.package_install('python3')! } else if pl == .osx { - osal.package_install('pipx,sqlite')! + osal.package_install('python@3.12')! } else { - return error('only support osx, arch & ubuntu.') + return error('only support osx & ubuntu.') + } + osal.execute_silent('curl -LsSf https://astral.sh/uv/install.sh | sh')! + if pl == .ubuntu { + osal.execute_silent('echo \'eval "$(uvx --generate-shell-completion bash)"\' >> ~/.bashrc')! + } else if pl == .osx { + osal.execute_silent('echo \'eval "$(uvx --generate-shell-completion bash)"\' >> ~/.bashrc')! + osal.execute_silent('echo \'eval "$(uvx --generate-shell-completion bash)"\' >> ~/.zshrc')! + } else { + return error('only support osx & ubuntu.') } - osal.execute_silent('pipx install uv')! } fn destroy() ! { - console.print_header('destroy python') - osal.package_remove('python3')! - pl := core.platform()! - if pl == .arch { - osal.package_remove('pipx,sqlite')! - } else if pl == .ubuntu { - osal.package_remove('pipx,sqlite')! - } else if pl == .osx { - osal.package_remove('pipx,sqlite')! - } else { - return error('only support osx, arch & ubuntu.') - } + console.print_header('remove python uv') + + // //will remove all paths where go/bin is found + // osal.profile_path_add_remove(paths2delete:"go/bin")! + + dir1 := osal.exec_fast(cmd: 'uv python dir', notempty: true)! + dir2 := osal.exec_fast(cmd: 'uv tool dir', notempty: true)! + dir3 := osal.exec_fast(cmd: 'uv cache dir', notempty: true)! + + osal.execute_silent(' + + uv cache clean + rm -rf "${dir1}" + rm -rf "${dir2}" + rm -rf "${dir3}" + rm ~/.local/bin/uv ~/.local/bin/uvx + ')! + osal.rm(' + uv + uvx + ')! } diff --git a/lib/installers/lang/python/python_factory_.v b/lib/installers/lang/python/python_factory_.v index 6b5c95d5..8a5df925 100644 --- a/lib/installers/lang/python/python_factory_.v +++ b/lib/installers/lang/python/python_factory_.v @@ -2,11 +2,11 @@ module python import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( - python_global map[string]&Python + python_global map[string]&PythonInstaller python_default string ) @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Python { - return &Python{} +pub fn new(args ArgsGet) !&PythonInstaller { + return &PythonInstaller{} +} + +pub fn get(args ArgsGet) !&PythonInstaller { + return new(args)! } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'python.') { + return + } + mut install_actions := plbook.find(filter: 'python.configure')! + if install_actions.len > 0 { + return error("can't configure python, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'python.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,54 +55,24 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: reset bool } -pub fn (mut self Python) install(args InstallArgs) ! { +pub fn (mut self PythonInstaller) install(args InstallArgs) ! { switch(self.name) if args.reset || (!installed()!) { install()! } } -pub fn (mut self Python) destroy() ! { +pub fn (mut self PythonInstaller) destroy() ! { switch(self.name) destroy()! } // switch instance to be used for python pub fn switch(name string) { - python_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/lang/python/python_model.v b/lib/installers/lang/python/python_model.v index 28f701b8..ee8818f4 100644 --- a/lib/installers/lang/python/python_model.v +++ b/lib/installers/lang/python/python_model.v @@ -2,19 +2,19 @@ module python import freeflowuniverse.herolib.data.encoderhero -pub const version = '3.12.0' -const singleton = true +pub const version = '0.8.11' +const singleton = false const default = true // THIS THE THE SOURCE OF THE INFORMATION OF THIS FILE, HERE WE HAVE THE CONFIG OBJECT CONFIGURED AND MODELLED @[heap] -pub struct Python { +pub struct PythonInstaller { pub mut: name string = 'default' } // your checking & initialization code if needed -fn obj_init(mycfg_ Python) !Python { +fn obj_init(mycfg_ PythonInstaller) !PythonInstaller { mut mycfg := mycfg_ return mycfg } @@ -26,11 +26,11 @@ fn configure() ! { /////////////NORMALLY NO NEED TO TOUCH -pub fn heroscript_dumps(obj Python) !string { - return encoderhero.encode[Python](obj)! +pub fn heroscript_dumps(obj PythonInstaller) !string { + return encoderhero.encode[PythonInstaller](obj)! } -pub fn heroscript_loads(heroscript string) !Python { - mut obj := encoderhero.decode[Python](heroscript)! +pub fn heroscript_loads(heroscript string) !PythonInstaller { + mut obj := encoderhero.decode[PythonInstaller](heroscript)! return obj } diff --git a/lib/installers/lang/rust/rust_factory_.v b/lib/installers/lang/rust/rust_factory_.v index 6137693f..09ea1c76 100644 --- a/lib/installers/lang/rust/rust_factory_.v +++ b/lib/installers/lang/rust/rust_factory_.v @@ -2,8 +2,8 @@ module rust import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( rust_global map[string]&RustInstaller @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&RustInstaller { +pub fn new(args ArgsGet) !&RustInstaller { return &RustInstaller{} } +pub fn get(args ArgsGet) !&RustInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'rust.') { + return + } + mut install_actions := plbook.find(filter: 'rust.configure')! + if install_actions.len > 0 { + return error("can't configure rust, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'rust.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -86,12 +75,4 @@ pub fn (mut self RustInstaller) destroy() ! { // switch instance to be used for rust pub fn switch(name string) { - rust_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/lang/vlang/vlang.v b/lib/installers/lang/vlang/vlang.v index ffdeddab..f9fe0840 100644 --- a/lib/installers/lang/vlang/vlang.v +++ b/lib/installers/lang/vlang/vlang.v @@ -38,7 +38,7 @@ pub fn install(args_ InstallArgs) ! { base.develop()! - mut gs := gittools.get(coderoot: '${os.home_dir()}/_code')! + mut gs := gittools.new(coderoot: '${os.home_dir()}/_code')! mut repo := gs.get_repo( pull: true reset: true diff --git a/lib/installers/net/mycelium_installer/mycelium_installer_actions.v b/lib/installers/net/mycelium_installer/mycelium_installer_actions.v index d6eef8cf..dbfadd7e 100644 --- a/lib/installers/net/mycelium_installer/mycelium_installer_actions.v +++ b/lib/installers/net/mycelium_installer/mycelium_installer_actions.v @@ -5,30 +5,33 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core import freeflowuniverse.herolib.core.pathlib -import freeflowuniverse.herolib.installers.infra.zinit_installer import freeflowuniverse.herolib.clients.mycelium import freeflowuniverse.herolib.develop.gittools -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.rust import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} mut peers_str := installer.peers.join(' ') mut tun_name := 'tun${installer.tun_nr}' - res << zinit.ZProcessNewArgs{ - name: 'mycelium' - startuptype: .zinit - cmd: 'mycelium --key-file ${osal.hero_path()!}/cfg/priv_key.bin --peers ${peers_str} --tun-name ${tun_name}' - env: { - 'HOME': '/root' - } + mut cmd:='mycelium --key-file ${osal.hero_path()!}/cfg/priv_key.bin --peers ${peers_str} --tun-name ${tun_name}' + if core.is_osx()! { + cmd = "sudo ${cmd}" } + res << startupmanager.ZProcessNewArgs{ + name: 'mycelium' + startuptype: .zinit + cmd: cmd + env: { + 'HOME': os.home_dir() + } + } return res } diff --git a/lib/installers/net/mycelium_installer/mycelium_installer_factory_.v b/lib/installers/net/mycelium_installer/mycelium_installer_factory_.v index 7a3ba437..e6d09fa0 100644 --- a/lib/installers/net/mycelium_installer/mycelium_installer_factory_.v +++ b/lib/installers/net/mycelium_installer/mycelium_installer_factory_.v @@ -3,8 +3,8 @@ module mycelium_installer import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&MyceliumInstaller { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&MyceliumInstaller { mut obj := MyceliumInstaller{ name: args.name } - if args.name !in mycelium_installer_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&MyceliumInstaller { + mut context := base.context()! + mycelium_installer_default = args.name + if args.fromdb || args.name !in mycelium_installer_global { + mut r := context.redis()! + if r.hexists('context:mycelium_installer', args.name)! { + data := r.hget('context:mycelium_installer', args.name)! + if data.len == 0 { + return error('MyceliumInstaller with name: mycelium_installer does not exist, prob bug.') + } + mut obj := json.decode(MyceliumInstaller, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('mycelium_installer', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("MyceliumInstaller with name 'mycelium_installer' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return mycelium_installer_global[args.name] or { - println(mycelium_installer_global) - // bug if we get here because should be in globals - panic('could not get config for mycelium_installer with name, is bug:${args.name}') + return error('could not get config for mycelium_installer with name:mycelium_installer') } } // register the config for the future pub fn set(o MyceliumInstaller) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + mycelium_installer_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('mycelium_installer', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:mycelium_installer', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('mycelium_installer', args.name) + mut r := context.redis()! + return r.hexists('context:mycelium_installer', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('mycelium_installer', args.name)! - if args.name in mycelium_installer_global { - // del mycelium_installer_global[args.name] + mut r := context.redis()! + r.hdel('context:mycelium_installer', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&MyceliumInstaller { + mut res := []&MyceliumInstaller{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + mycelium_installer_global = map[string]&MyceliumInstaller{} + mycelium_installer_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:mycelium_installer')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in mycelium_installer_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o MyceliumInstaller) ! { +fn set_in_mem(o MyceliumInstaller) !MyceliumInstaller { mut o2 := obj_init(o)! - mycelium_installer_global[o.name] = &o2 - mycelium_installer_default = o.name + mycelium_installer_global[o2.name] = &o2 + mycelium_installer_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'mycelium_installer.') { + return + } mut install_actions := plbook.find(filter: 'mycelium_installer.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'mycelium_installer.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,24 +170,28 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: screen') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } @@ -175,7 +218,10 @@ pub fn (mut self MyceliumInstaller) start() ! { start_pre()! + + for zprocess in startupcmd()! { + mut sm := startupmanager_get(zprocess.startuptype)! console.print_debug('starting mycelium_installer with ${zprocess.startuptype}...') @@ -183,6 +229,7 @@ pub fn (mut self MyceliumInstaller) start() ! { sm.new(zprocess)! sm.start(zprocess.name)! + } start_post()! @@ -223,10 +270,12 @@ pub fn (mut self MyceliumInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -260,10 +309,3 @@ pub fn (mut self MyceliumInstaller) destroy() ! { pub fn switch(name string) { mycelium_installer_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/net/wireguard_installer/wireguard_installer_factory_.v b/lib/installers/net/wireguard_installer/wireguard_installer_factory_.v index a3e9cddb..90876b5b 100644 --- a/lib/installers/net/wireguard_installer/wireguard_installer_factory_.v +++ b/lib/installers/net/wireguard_installer/wireguard_installer_factory_.v @@ -2,8 +2,8 @@ module wireguard_installer import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( wireguard_installer_global map[string]&WireGuard @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&WireGuard { +pub fn new(args ArgsGet) !&WireGuard { return &WireGuard{} } +pub fn get(args ArgsGet) !&WireGuard { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'wireguard_installer.') { + return + } + mut install_actions := plbook.find(filter: 'wireguard_installer.configure')! + if install_actions.len > 0 { + return error("can't configure wireguard_installer, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'wireguard_installer.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -88,10 +77,3 @@ pub fn (mut self WireGuard) destroy() ! { pub fn switch(name string) { wireguard_installer_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/net/yggdrasil/ygg.v b/lib/installers/net/yggdrasil/ygg.v index 353a0804..25e10299 100644 --- a/lib/installers/net/yggdrasil/ygg.v +++ b/lib/installers/net/yggdrasil/ygg.v @@ -41,7 +41,7 @@ Peers: if args.reset { golang.install()! console.print_header('install yggdrasil') - mut gs := gittools.get(coderoot: '${os.home_dir()}/_code')! + mut gs := gittools.new(coderoot: '${os.home_dir()}/_code')! mut repo := gs.get_repo( url: 'https://github.com/yggdrasil-network/yggdrasil-go.git' reset: false diff --git a/lib/installers/net/yggdrasil/yggdrasil_actions.v b/lib/installers/net/yggdrasil/yggdrasil_actions.v index 6a191e31..08f34df2 100644 --- a/lib/installers/net/yggdrasil/yggdrasil_actions.v +++ b/lib/installers/net/yggdrasil/yggdrasil_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'yggdrasil' // cmd: 'yggdrasil server' // env: { diff --git a/lib/installers/net/yggdrasil/yggdrasil_factory_.v b/lib/installers/net/yggdrasil/yggdrasil_factory_.v index f2ba112c..066a2ec6 100644 --- a/lib/installers/net/yggdrasil/yggdrasil_factory_.v +++ b/lib/installers/net/yggdrasil/yggdrasil_factory_.v @@ -2,8 +2,8 @@ module yggdrasil import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&YggdrasilInstaller { +pub fn new(args ArgsGet) !&YggdrasilInstaller { return &YggdrasilInstaller{} } +pub fn get(args ArgsGet) !&YggdrasilInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'yggdrasil.') { + return + } + mut install_actions := plbook.find(filter: 'yggdrasil.configure')! + if install_actions.len > 0 { + return error("can't configure yggdrasil, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'yggdrasil.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self YggdrasilInstaller) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self YggdrasilInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self YggdrasilInstaller) destroy() ! { // switch instance to be used for yggdrasil pub fn switch(name string) { - yggdrasil_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/actrunner/actrunner_actions.v b/lib/installers/sysadmintools/actrunner/actrunner_actions.v index 65f78458..93edfb36 100644 --- a/lib/installers/sysadmintools/actrunner/actrunner_actions.v +++ b/lib/installers/sysadmintools/actrunner/actrunner_actions.v @@ -2,15 +2,15 @@ module actrunner import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.core import os -fn startupcmd() ![]zinit.ZProcessNewArgs { - mut res := []zinit.ZProcessNewArgs{} - res << zinit.ZProcessNewArgs{ +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} + res << startupmanager.ZProcessNewArgs{ name: 'actrunner' cmd: 'actrunner daemon' startuptype: .zinit diff --git a/lib/installers/sysadmintools/actrunner/actrunner_factory_.v b/lib/installers/sysadmintools/actrunner/actrunner_factory_.v index be54d827..9bdb1e06 100644 --- a/lib/installers/sysadmintools/actrunner/actrunner_factory_.v +++ b/lib/installers/sysadmintools/actrunner/actrunner_factory_.v @@ -2,8 +2,8 @@ module actrunner import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&ActRunner { +pub fn new(args ArgsGet) !&ActRunner { return &ActRunner{} } +pub fn get(args ArgsGet) !&ActRunner { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'actrunner.') { + return + } + mut install_actions := plbook.find(filter: 'actrunner.configure')! + if install_actions.len > 0 { + return error("can't configure actrunner, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'actrunner.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self ActRunner) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self ActRunner) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self ActRunner) destroy() ! { // switch instance to be used for actrunner pub fn switch(name string) { - actrunner_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/b2/b2_factory_.v b/lib/installers/sysadmintools/b2/b2_factory_.v index 27d828df..649206a5 100644 --- a/lib/installers/sysadmintools/b2/b2_factory_.v +++ b/lib/installers/sysadmintools/b2/b2_factory_.v @@ -2,8 +2,8 @@ module b2 import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( b2_global map[string]&BackBase @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&BackBase { +pub fn new(args ArgsGet) !&BackBase { return &BackBase{} } +pub fn get(args ArgsGet) !&BackBase { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'b2.') { + return + } + mut install_actions := plbook.find(filter: 'b2.configure')! + if install_actions.len > 0 { + return error("can't configure b2, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'b2.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -86,12 +75,4 @@ pub fn (mut self BackBase) destroy() ! { // switch instance to be used for b2 pub fn switch(name string) { - b2_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/daguserver/daguserver_actions.v b/lib/installers/sysadmintools/daguserver/daguserver_actions.v index 1dcc0a92..11160037 100644 --- a/lib/installers/sysadmintools/daguserver/daguserver_actions.v +++ b/lib/installers/sysadmintools/daguserver/daguserver_actions.v @@ -7,14 +7,14 @@ import freeflowuniverse.herolib.core import freeflowuniverse.herolib.core.httpconnection import freeflowuniverse.herolib.installers.ulist // import freeflowuniverse.herolib.develop.gittools -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import os -fn startupcmd() ![]zinit.ZProcessNewArgs { - mut res := []zinit.ZProcessNewArgs{} +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} mut cfg := get()! - res << zinit.ZProcessNewArgs{ + res << startupmanager.ZProcessNewArgs{ name: 'dagu' cmd: 'dagu server' env: { @@ -23,7 +23,7 @@ fn startupcmd() ![]zinit.ZProcessNewArgs { } } - res << zinit.ZProcessNewArgs{ + res << startupmanager.ZProcessNewArgs{ name: 'dagu_scheduler' cmd: 'dagu scheduler' env: { diff --git a/lib/installers/sysadmintools/daguserver/daguserver_factory_.v b/lib/installers/sysadmintools/daguserver/daguserver_factory_.v index 95b00690..9967c8ab 100644 --- a/lib/installers/sysadmintools/daguserver/daguserver_factory_.v +++ b/lib/installers/sysadmintools/daguserver/daguserver_factory_.v @@ -3,8 +3,8 @@ module daguserver import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&DaguInstaller { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&DaguInstaller { mut obj := DaguInstaller{ name: args.name } - if args.name !in daguserver_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&DaguInstaller { + mut context := base.context()! + daguserver_default = args.name + if args.fromdb || args.name !in daguserver_global { + mut r := context.redis()! + if r.hexists('context:daguserver', args.name)! { + data := r.hget('context:daguserver', args.name)! + if data.len == 0 { + return error('DaguInstaller with name: daguserver does not exist, prob bug.') + } + mut obj := json.decode(DaguInstaller, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('daguserver', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("DaguInstaller with name 'daguserver' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return daguserver_global[args.name] or { - println(daguserver_global) - // bug if we get here because should be in globals - panic('could not get config for daguserver with name, is bug:${args.name}') + return error('could not get config for daguserver with name:daguserver') } } // register the config for the future pub fn set(o DaguInstaller) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + daguserver_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('daguserver', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:daguserver', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('daguserver', args.name) + mut r := context.redis()! + return r.hexists('context:daguserver', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('daguserver', args.name)! - if args.name in daguserver_global { - // del daguserver_global[args.name] + mut r := context.redis()! + r.hdel('context:daguserver', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&DaguInstaller { + mut res := []&DaguInstaller{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + daguserver_global = map[string]&DaguInstaller{} + daguserver_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:daguserver')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in daguserver_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o DaguInstaller) ! { +fn set_in_mem(o DaguInstaller) !DaguInstaller { mut o2 := obj_init(o)! - daguserver_global[o.name] = &o2 - daguserver_default = o.name + daguserver_global[o2.name] = &o2 + daguserver_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'daguserver.') { + return + } mut install_actions := plbook.find(filter: 'daguserver.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'daguserver.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,36 +170,38 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } // load from disk and make sure is properly intialized pub fn (mut self DaguInstaller) reload() ! { - switch(self.name) self = obj_init(self)! } pub fn (mut self DaguInstaller) start() ! { - switch(self.name) if self.running()! { return } @@ -223,10 +264,12 @@ pub fn (mut self DaguInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -253,12 +296,4 @@ pub fn (mut self DaguInstaller) destroy() ! { // switch instance to be used for daguserver pub fn switch(name string) { - daguserver_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/fungistor/fungistor_actions.v b/lib/installers/sysadmintools/fungistor/fungistor_actions.v index abea2acc..a983bef9 100644 --- a/lib/installers/sysadmintools/fungistor/fungistor_actions.v +++ b/lib/installers/sysadmintools/fungistor/fungistor_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'fungistor' // cmd: 'fungistor server' // env: { diff --git a/lib/installers/sysadmintools/fungistor/fungistor_factory_.v b/lib/installers/sysadmintools/fungistor/fungistor_factory_.v index d0981bd5..ee14ef7c 100644 --- a/lib/installers/sysadmintools/fungistor/fungistor_factory_.v +++ b/lib/installers/sysadmintools/fungistor/fungistor_factory_.v @@ -2,8 +2,8 @@ module fungistor import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&FungiStor { +pub fn new(args ArgsGet) !&FungiStor { return &FungiStor{} } +pub fn get(args ArgsGet) !&FungiStor { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'fungistor.') { + return + } + mut install_actions := plbook.find(filter: 'fungistor.configure')! + if install_actions.len > 0 { + return error("can't configure fungistor, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'fungistor.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self FungiStor) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self FungiStor) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self FungiStor) destroy() ! { // switch instance to be used for fungistor pub fn switch(name string) { - fungistor_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/garage_s3/garage_s3_actions.v b/lib/installers/sysadmintools/garage_s3/garage_s3_actions.v index 45836fc1..f710a5fb 100644 --- a/lib/installers/sysadmintools/garage_s3/garage_s3_actions.v +++ b/lib/installers/sysadmintools/garage_s3/garage_s3_actions.v @@ -4,15 +4,15 @@ import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.core.httpconnection import os import json -fn startupcmd() ![]zinit.ZProcessNewArgs { - mut res := []zinit.ZProcessNewArgs{} - res << zinit.ZProcessNewArgs{ +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} + res << startupmanager.ZProcessNewArgs{ name: 'garage_s3' cmd: 'garage_s3 -c /var/garage/config.toml server' startuptype: .zinit diff --git a/lib/installers/sysadmintools/garage_s3/garage_s3_factory_.v b/lib/installers/sysadmintools/garage_s3/garage_s3_factory_.v index 46ee9703..42ec942e 100644 --- a/lib/installers/sysadmintools/garage_s3/garage_s3_factory_.v +++ b/lib/installers/sysadmintools/garage_s3/garage_s3_factory_.v @@ -3,8 +3,8 @@ module garage_s3 import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&GarageS3 { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&GarageS3 { mut obj := GarageS3{ name: args.name } - if args.name !in garage_s3_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&GarageS3 { + mut context := base.context()! + garage_s3_default = args.name + if args.fromdb || args.name !in garage_s3_global { + mut r := context.redis()! + if r.hexists('context:garage_s3', args.name)! { + data := r.hget('context:garage_s3', args.name)! + if data.len == 0 { + return error('GarageS3 with name: garage_s3 does not exist, prob bug.') + } + mut obj := json.decode(GarageS3, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('garage_s3', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("GarageS3 with name 'garage_s3' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return garage_s3_global[args.name] or { - println(garage_s3_global) - // bug if we get here because should be in globals - panic('could not get config for garage_s3 with name, is bug:${args.name}') + return error('could not get config for garage_s3 with name:garage_s3') } } // register the config for the future pub fn set(o GarageS3) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + garage_s3_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('garage_s3', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:garage_s3', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('garage_s3', args.name) + mut r := context.redis()! + return r.hexists('context:garage_s3', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('garage_s3', args.name)! - if args.name in garage_s3_global { - // del garage_s3_global[args.name] + mut r := context.redis()! + r.hdel('context:garage_s3', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&GarageS3 { + mut res := []&GarageS3{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + garage_s3_global = map[string]&GarageS3{} + garage_s3_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:garage_s3')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in garage_s3_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o GarageS3) ! { +fn set_in_mem(o GarageS3) !GarageS3 { mut o2 := obj_init(o)! - garage_s3_global[o.name] = &o2 - garage_s3_default = o.name + garage_s3_global[o2.name] = &o2 + garage_s3_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'garage_s3.') { + return + } mut install_actions := plbook.find(filter: 'garage_s3.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'garage_s3.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,24 +170,28 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } @@ -223,10 +266,12 @@ pub fn (mut self GarageS3) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -255,10 +300,3 @@ pub fn (mut self GarageS3) destroy() ! { pub fn switch(name string) { garage_s3_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/sysadmintools/grafana/grafana_actions.v b/lib/installers/sysadmintools/grafana/grafana_actions.v index 759c0476..c2dc30c9 100644 --- a/lib/installers/sysadmintools/grafana/grafana_actions.v +++ b/lib/installers/sysadmintools/grafana/grafana_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'grafana' // cmd: 'grafana server' // env: { diff --git a/lib/installers/sysadmintools/grafana/grafana_factory_.v b/lib/installers/sysadmintools/grafana/grafana_factory_.v index b529bf6b..fa7b0359 100644 --- a/lib/installers/sysadmintools/grafana/grafana_factory_.v +++ b/lib/installers/sysadmintools/grafana/grafana_factory_.v @@ -2,8 +2,8 @@ module grafana import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Grafana { +pub fn new(args ArgsGet) !&Grafana { return &Grafana{} } +pub fn get(args ArgsGet) !&Grafana { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'grafana.') { + return + } + mut install_actions := plbook.find(filter: 'grafana.configure')! + if install_actions.len > 0 { + return error("can't configure grafana, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'grafana.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self Grafana) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self Grafana) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self Grafana) destroy() ! { // switch instance to be used for grafana pub fn switch(name string) { - grafana_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/prometheus/prometheus_actions.v b/lib/installers/sysadmintools/prometheus/prometheus_actions.v index fa2c8350..242e3acd 100644 --- a/lib/installers/sysadmintools/prometheus/prometheus_actions.v +++ b/lib/installers/sysadmintools/prometheus/prometheus_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'prometheus' // cmd: 'prometheus server' // env: { diff --git a/lib/installers/sysadmintools/prometheus/prometheus_factory_.v b/lib/installers/sysadmintools/prometheus/prometheus_factory_.v index 3a948f60..90bf0906 100644 --- a/lib/installers/sysadmintools/prometheus/prometheus_factory_.v +++ b/lib/installers/sysadmintools/prometheus/prometheus_factory_.v @@ -2,8 +2,8 @@ module prometheus import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Prometheus { +pub fn new(args ArgsGet) !&Prometheus { return &Prometheus{} } +pub fn get(args ArgsGet) !&Prometheus { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'prometheus.') { + return + } + mut install_actions := plbook.find(filter: 'prometheus.configure')! + if install_actions.len > 0 { + return error("can't configure prometheus, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'prometheus.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self Prometheus) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self Prometheus) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self Prometheus) destroy() ! { // switch instance to be used for prometheus pub fn switch(name string) { - prometheus_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/rclone/rclone_factory_.v b/lib/installers/sysadmintools/rclone/rclone_factory_.v index ebfc0b80..8456adac 100644 --- a/lib/installers/sysadmintools/rclone/rclone_factory_.v +++ b/lib/installers/sysadmintools/rclone/rclone_factory_.v @@ -3,8 +3,8 @@ module rclone import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( rclone_global map[string]&RClone @@ -16,71 +16,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&RClone { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&RClone { mut obj := RClone{ name: args.name } - if args.name !in rclone_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&RClone { + mut context := base.context()! + rclone_default = args.name + if args.fromdb || args.name !in rclone_global { + mut r := context.redis()! + if r.hexists('context:rclone', args.name)! { + data := r.hget('context:rclone', args.name)! + if data.len == 0 { + return error('RClone with name: rclone does not exist, prob bug.') + } + mut obj := json.decode(RClone, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('rclone', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("RClone with name 'rclone' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return rclone_global[args.name] or { - println(rclone_global) - // bug if we get here because should be in globals - panic('could not get config for rclone with name, is bug:${args.name}') + return error('could not get config for rclone with name:rclone') } } // register the config for the future pub fn set(o RClone) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + rclone_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('rclone', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:rclone', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('rclone', args.name) + mut r := context.redis()! + return r.hexists('context:rclone', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('rclone', args.name)! - if args.name in rclone_global { - // del rclone_global[args.name] + mut r := context.redis()! + r.hdel('context:rclone', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&RClone { + mut res := []&RClone{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + rclone_global = map[string]&RClone{} + rclone_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:rclone')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in rclone_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o RClone) ! { +fn set_in_mem(o RClone) !RClone { mut o2 := obj_init(o)! - rclone_global[o.name] = &o2 - rclone_default = o.name + rclone_global[o2.name] = &o2 + rclone_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'rclone.') { + return + } mut install_actions := plbook.find(filter: 'rclone.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -89,7 +129,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'rclone.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -111,28 +150,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - // load from disk and make sure is properly intialized pub fn (mut self RClone) reload() ! { switch(self.name) @@ -161,10 +178,3 @@ pub fn (mut self RClone) destroy() ! { pub fn switch(name string) { rclone_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/sysadmintools/restic/builder.v b/lib/installers/sysadmintools/restic/builder.v index 14f1037a..7722fa3b 100644 --- a/lib/installers/sysadmintools/restic/builder.v +++ b/lib/installers/sysadmintools/restic/builder.v @@ -26,7 +26,7 @@ pub fn build_(args BuildArgs) ! { // install restic if it was already done will return true console.print_header('build restic') - mut gs := gittools.get(coderoot: '/tmp/builder')! + mut gs := gittools.new(coderoot: '/tmp/builder')! mut repo := gs.get_repo( url: url reset: true diff --git a/lib/installers/sysadmintools/restic/restic_actions.v b/lib/installers/sysadmintools/restic/restic_actions.v index a5d7cb3a..1d3a5e47 100644 --- a/lib/installers/sysadmintools/restic/restic_actions.v +++ b/lib/installers/sysadmintools/restic/restic_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'restic' // cmd: 'restic server' // env: { diff --git a/lib/installers/sysadmintools/restic/restic_factory_.v b/lib/installers/sysadmintools/restic/restic_factory_.v index 892d109d..88cc4c99 100644 --- a/lib/installers/sysadmintools/restic/restic_factory_.v +++ b/lib/installers/sysadmintools/restic/restic_factory_.v @@ -2,8 +2,8 @@ module restic import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Restic { +pub fn new(args ArgsGet) !&Restic { return &Restic{} } +pub fn get(args ArgsGet) !&Restic { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'restic.') { + return + } + mut install_actions := plbook.find(filter: 'restic.configure')! + if install_actions.len > 0 { + return error("can't configure restic, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'restic.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self Restic) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self Restic) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self Restic) destroy() ! { // switch instance to be used for restic pub fn switch(name string) { - restic_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/s3/builder.v b/lib/installers/sysadmintools/s3/builder.v index 485edc80..233ea695 100644 --- a/lib/installers/sysadmintools/s3/builder.v +++ b/lib/installers/sysadmintools/s3/builder.v @@ -25,7 +25,7 @@ pub fn build_(args BuildArgs) ! { osal.package_install('libssl-dev,pkg-config')! - mut gs := gittools.get()! + mut gs := gittools.new()! mut repo := gs.get_repo( url: 'https://github.com/leesmet/s3-cas' reset: false diff --git a/lib/installers/sysadmintools/s3/s3_actions.v b/lib/installers/sysadmintools/s3/s3_actions.v index f5168020..d3868abd 100644 --- a/lib/installers/sysadmintools/s3/s3_actions.v +++ b/lib/installers/sysadmintools/s3/s3_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 's3' // cmd: 's3 server' // env: { diff --git a/lib/installers/sysadmintools/s3/s3_factory_.v b/lib/installers/sysadmintools/s3/s3_factory_.v index cb73988f..367abeec 100644 --- a/lib/installers/sysadmintools/s3/s3_factory_.v +++ b/lib/installers/sysadmintools/s3/s3_factory_.v @@ -2,8 +2,8 @@ module s3 import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&S3Installer { +pub fn new(args ArgsGet) !&S3Installer { return &S3Installer{} } +pub fn get(args ArgsGet) !&S3Installer { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 's3.') { + return + } + mut install_actions := plbook.find(filter: 's3.configure')! + if install_actions.len > 0 { + return error("can't configure s3, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 's3.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self S3Installer) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self S3Installer) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self S3Installer) destroy() ! { // switch instance to be used for s3 pub fn switch(name string) { - s3_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/sysadmintools/s3/server.v b/lib/installers/sysadmintools/s3/server.v index 3235f9ea..0cf7c2dc 100644 --- a/lib/installers/sysadmintools/s3/server.v +++ b/lib/installers/sysadmintools/s3/server.v @@ -1,7 +1,7 @@ module s3 import freeflowuniverse.herolib.osal.core as osal -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.data.dbfs import freeflowuniverse.herolib.core.texttools import json diff --git a/lib/installers/threefold/griddriver/griddriver_factory_.v b/lib/installers/threefold/griddriver/griddriver_factory_.v index caa721c3..8068cb8f 100644 --- a/lib/installers/threefold/griddriver/griddriver_factory_.v +++ b/lib/installers/threefold/griddriver/griddriver_factory_.v @@ -2,8 +2,8 @@ module griddriver import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( griddriver_global map[string]&GridDriverInstaller @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&GridDriverInstaller { +pub fn new(args ArgsGet) !&GridDriverInstaller { return &GridDriverInstaller{} } +pub fn get(args ArgsGet) !&GridDriverInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'griddriver.') { + return + } + mut install_actions := plbook.find(filter: 'griddriver.configure')! + if install_actions.len > 0 { + return error("can't configure griddriver, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'griddriver.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -91,12 +80,4 @@ pub fn (mut self GridDriverInstaller) destroy() ! { // switch instance to be used for griddriver pub fn switch(name string) { - griddriver_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/threefold/tfrobot/tfrobot.v b/lib/installers/threefold/tfrobot/tfrobot.v index 39058086..2ec4e0ad 100644 --- a/lib/installers/threefold/tfrobot/tfrobot.v +++ b/lib/installers/threefold/tfrobot/tfrobot.v @@ -48,7 +48,7 @@ pub fn build_() ! { dest_on_os = '/usr/local/bin' } - mut gs := gittools.get()! + mut gs := gittools.new()! mut repo := gs.get_repo( url: 'https://github.com/threefoldtech/tfgrid-sdk-go' reset: true diff --git a/lib/installers/virt/cloudhypervisor/cloudhypervisor_factory_.v b/lib/installers/virt/cloudhypervisor/cloudhypervisor_factory_.v index 9cdac666..32def1bc 100644 --- a/lib/installers/virt/cloudhypervisor/cloudhypervisor_factory_.v +++ b/lib/installers/virt/cloudhypervisor/cloudhypervisor_factory_.v @@ -2,8 +2,8 @@ module cloudhypervisor import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( cloudhypervisor_global map[string]&CloudHypervisor @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&CloudHypervisor { +pub fn new(args ArgsGet) !&CloudHypervisor { return &CloudHypervisor{} } +pub fn get(args ArgsGet) !&CloudHypervisor { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'cloudhypervisor.') { + return + } + mut install_actions := plbook.find(filter: 'cloudhypervisor.configure')! + if install_actions.len > 0 { + return error("can't configure cloudhypervisor, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'cloudhypervisor.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -91,12 +80,4 @@ pub fn (mut self CloudHypervisor) destroy() ! { // switch instance to be used for cloudhypervisor pub fn switch(name string) { - cloudhypervisor_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/virt/docker/docker_actions.v b/lib/installers/virt/docker/docker_actions.v index 99a359de..e5671320 100644 --- a/lib/installers/virt/docker/docker_actions.v +++ b/lib/installers/virt/docker/docker_actions.v @@ -3,12 +3,12 @@ module docker import freeflowuniverse.herolib.core import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.ui.console -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist -fn startupcmd() ![]zinit.ZProcessNewArgs { - mut res := []zinit.ZProcessNewArgs{} - res << zinit.ZProcessNewArgs{ +fn startupcmd() ![]startupmanager.ZProcessNewArgs { + mut res := []startupmanager.ZProcessNewArgs{} + res << startupmanager.ZProcessNewArgs{ name: 'docker' cmd: 'dockerd' } diff --git a/lib/installers/virt/docker/docker_factory_.v b/lib/installers/virt/docker/docker_factory_.v index e25e7205..14235f34 100644 --- a/lib/installers/virt/docker/docker_factory_.v +++ b/lib/installers/virt/docker/docker_factory_.v @@ -2,8 +2,8 @@ module docker import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&DockerInstaller { +pub fn new(args ArgsGet) !&DockerInstaller { return &DockerInstaller{} } +pub fn get(args ArgsGet) !&DockerInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'docker.') { + return + } + mut install_actions := plbook.find(filter: 'docker.configure')! + if install_actions.len > 0 { + return error("can't configure docker, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'docker.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,24 +75,28 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } @@ -150,10 +165,12 @@ pub fn (mut self DockerInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -182,10 +199,3 @@ pub fn (mut self DockerInstaller) destroy() ! { pub fn switch(name string) { docker_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/virt/pacman/pacman_factory_.v b/lib/installers/virt/pacman/pacman_factory_.v index 6e333e9c..30e6c47e 100644 --- a/lib/installers/virt/pacman/pacman_factory_.v +++ b/lib/installers/virt/pacman/pacman_factory_.v @@ -2,8 +2,8 @@ module pacman import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( pacman_global map[string]&PacmanInstaller @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&PacmanInstaller { +pub fn new(args ArgsGet) !&PacmanInstaller { return &PacmanInstaller{} } +pub fn get(args ArgsGet) !&PacmanInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'pacman.') { + return + } + mut install_actions := plbook.find(filter: 'pacman.configure')! + if install_actions.len > 0 { + return error("can't configure pacman, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'pacman.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -86,12 +75,4 @@ pub fn (mut self PacmanInstaller) destroy() ! { // switch instance to be used for pacman pub fn switch(name string) { - pacman_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/virt/podman/podman_factory_.v b/lib/installers/virt/podman/podman_factory_.v index 2e859a2b..1349142e 100644 --- a/lib/installers/virt/podman/podman_factory_.v +++ b/lib/installers/virt/podman/podman_factory_.v @@ -2,8 +2,8 @@ module podman import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( podman_global map[string]&PodmanInstaller @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&PodmanInstaller { +pub fn new(args ArgsGet) !&PodmanInstaller { return &PodmanInstaller{} } +pub fn get(args ArgsGet) !&PodmanInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'podman.') { + return + } + mut install_actions := plbook.find(filter: 'podman.configure')! + if install_actions.len > 0 { + return error("can't configure podman, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'podman.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -86,12 +75,4 @@ pub fn (mut self PodmanInstaller) destroy() ! { // switch instance to be used for podman pub fn switch(name string) { - podman_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/virt/youki/youki_factory_.v b/lib/installers/virt/youki/youki_factory_.v index f7494a09..bed16b94 100644 --- a/lib/installers/virt/youki/youki_factory_.v +++ b/lib/installers/virt/youki/youki_factory_.v @@ -2,8 +2,8 @@ module youki import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( youki_global map[string]&YoukiInstaller @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&YoukiInstaller { +pub fn new(args ArgsGet) !&YoukiInstaller { return &YoukiInstaller{} } +pub fn get(args ArgsGet) !&YoukiInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'youki.') { + return + } + mut install_actions := plbook.find(filter: 'youki.configure')! + if install_actions.len > 0 { + return error("can't configure youki, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'youki.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -91,12 +80,4 @@ pub fn (mut self YoukiInstaller) destroy() ! { // switch instance to be used for youki pub fn switch(name string) { - youki_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/web/bun/bun_factory_.v b/lib/installers/web/bun/bun_factory_.v index 991f6147..6c47d073 100644 --- a/lib/installers/web/bun/bun_factory_.v +++ b/lib/installers/web/bun/bun_factory_.v @@ -2,8 +2,8 @@ module bun import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( bun_global map[string]&Bun @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Bun { +pub fn new(args ArgsGet) !&Bun { return &Bun{} } +pub fn get(args ArgsGet) !&Bun { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'bun.') { + return + } + mut install_actions := plbook.find(filter: 'bun.configure')! + if install_actions.len > 0 { + return error("can't configure bun, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'bun.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -86,12 +75,4 @@ pub fn (mut self Bun) destroy() ! { // switch instance to be used for bun pub fn switch(name string) { - bun_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/web/imagemagick/imagemagick_actions.v b/lib/installers/web/imagemagick/imagemagick_actions.v index 42030906..f1477b97 100644 --- a/lib/installers/web/imagemagick/imagemagick_actions.v +++ b/lib/installers/web/imagemagick/imagemagick_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'imagemagick' // cmd: 'imagemagick server' // env: { diff --git a/lib/installers/web/imagemagick/imagemagick_factory_.v b/lib/installers/web/imagemagick/imagemagick_factory_.v index f8e6efc0..98321fc6 100644 --- a/lib/installers/web/imagemagick/imagemagick_factory_.v +++ b/lib/installers/web/imagemagick/imagemagick_factory_.v @@ -2,8 +2,8 @@ module imagemagick import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&ImageMagick { +pub fn new(args ArgsGet) !&ImageMagick { return &ImageMagick{} } +pub fn get(args ArgsGet) !&ImageMagick { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'imagemagick.') { + return + } + mut install_actions := plbook.find(filter: 'imagemagick.configure')! + if install_actions.len > 0 { + return error("can't configure imagemagick, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'imagemagick.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self ImageMagick) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self ImageMagick) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self ImageMagick) destroy() ! { // switch instance to be used for imagemagick pub fn switch(name string) { - imagemagick_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/web/lighttpd/installer.v b/lib/installers/web/lighttpd/installer.v index 0eaefd35..09f84540 100644 --- a/lib/installers/web/lighttpd/installer.v +++ b/lib/installers/web/lighttpd/installer.v @@ -5,7 +5,7 @@ import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.installers.infra.zinit -import freeflowuniverse.herolib.osal.zinit as zinitmgmt +import freeflowuniverse.herolib.osal.startupmanager as zinitmgmt import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.screen import os diff --git a/lib/installers/web/lighttpd/lighttpd_actions.v b/lib/installers/web/lighttpd/lighttpd_actions.v index d64a1684..6cba21e1 100644 --- a/lib/installers/web/lighttpd/lighttpd_actions.v +++ b/lib/installers/web/lighttpd/lighttpd_actions.v @@ -5,18 +5,18 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust import freeflowuniverse.herolib.installers.lang.python import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} + mut res := []startupmanager.ZProcessNewArgs{} // THIS IS EXAMPLE CODEAND NEEDS TO BE CHANGED - // res << zinit.ZProcessNewArgs{ + // res << startupmanager.ZProcessNewArgs{ // name: 'lighttpd' // cmd: 'lighttpd server' // env: { diff --git a/lib/installers/web/lighttpd/lighttpd_factory_.v b/lib/installers/web/lighttpd/lighttpd_factory_.v index 0d392640..aa1afafd 100644 --- a/lib/installers/web/lighttpd/lighttpd_factory_.v +++ b/lib/installers/web/lighttpd/lighttpd_factory_.v @@ -2,8 +2,8 @@ module lighttpd import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -16,14 +16,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&LightHttpdInstaller { +pub fn new(args ArgsGet) !&LightHttpdInstaller { return &LightHttpdInstaller{} } +pub fn get(args ArgsGet) !&LightHttpdInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'lighttpd.') { + return + } + mut install_actions := plbook.find(filter: 'lighttpd.configure')! + if install_actions.len > 0 { + return error("can't configure lighttpd, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'lighttpd.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -64,30 +75,33 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } pub fn (mut self LightHttpdInstaller) start() ! { - switch(self.name) if self.running()! { return } @@ -150,10 +164,12 @@ pub fn (mut self LightHttpdInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -185,12 +201,4 @@ pub fn (mut self LightHttpdInstaller) destroy() ! { // switch instance to be used for lighttpd pub fn switch(name string) { - lighttpd_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/web/tailwind/tailwind_factory_.v b/lib/installers/web/tailwind/tailwind_factory_.v index a5a354f7..b08d4ce2 100644 --- a/lib/installers/web/tailwind/tailwind_factory_.v +++ b/lib/installers/web/tailwind/tailwind_factory_.v @@ -2,8 +2,8 @@ module tailwind import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( tailwind_global map[string]&Tailwind @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Tailwind { +pub fn new(args ArgsGet) !&Tailwind { return &Tailwind{} } +pub fn get(args ArgsGet) !&Tailwind { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'tailwind.') { + return + } + mut install_actions := plbook.find(filter: 'tailwind.configure')! + if install_actions.len > 0 { + return error("can't configure tailwind, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'tailwind.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -88,10 +77,3 @@ pub fn (mut self Tailwind) destroy() ! { pub fn switch(name string) { tailwind_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/web/tailwind4/tailwind4_factory_.v b/lib/installers/web/tailwind4/tailwind4_factory_.v index 75ae59e8..3ca1b96d 100644 --- a/lib/installers/web/tailwind4/tailwind4_factory_.v +++ b/lib/installers/web/tailwind4/tailwind4_factory_.v @@ -2,8 +2,8 @@ module tailwind4 import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( tailwind4_global map[string]&Tailwind @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&Tailwind { +pub fn new(args ArgsGet) !&Tailwind { return &Tailwind{} } +pub fn get(args ArgsGet) !&Tailwind { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'tailwind4.') { + return + } + mut install_actions := plbook.find(filter: 'tailwind4.configure')! + if install_actions.len > 0 { + return error("can't configure tailwind4, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'tailwind4.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -88,10 +77,3 @@ pub fn (mut self Tailwind) destroy() ! { pub fn switch(name string) { tailwind4_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/installers/web/traefik/traefik_actions.v b/lib/installers/web/traefik/traefik_actions.v index 90dd1c5b..3ff6d4e4 100644 --- a/lib/installers/web/traefik/traefik_actions.v +++ b/lib/installers/web/traefik/traefik_actions.v @@ -4,14 +4,14 @@ import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import os -fn startupcmd() ![]zinit.ZProcessNewArgs { +fn startupcmd() ![]startupmanager.ZProcessNewArgs { mut installer := get()! - mut res := []zinit.ZProcessNewArgs{} - res << zinit.ZProcessNewArgs{ + mut res := []startupmanager.ZProcessNewArgs{} + res << startupmanager.ZProcessNewArgs{ name: 'traefik' cmd: 'traefik' } diff --git a/lib/installers/web/traefik/traefik_factory_.v b/lib/installers/web/traefik/traefik_factory_.v index e9ba5510..3417428a 100644 --- a/lib/installers/web/traefik/traefik_factory_.v +++ b/lib/installers/web/traefik/traefik_factory_.v @@ -3,8 +3,8 @@ module traefik import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time __global ( @@ -17,71 +17,111 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&TraefikServer { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&TraefikServer { mut obj := TraefikServer{ name: args.name } - if args.name !in traefik_global { - if !exists(args)! { - set(obj)! + set(obj)! + return get(name: args.name)! +} + +pub fn get(args ArgsGet) !&TraefikServer { + mut context := base.context()! + traefik_default = args.name + if args.fromdb || args.name !in traefik_global { + mut r := context.redis()! + if r.hexists('context:traefik', args.name)! { + data := r.hget('context:traefik', args.name)! + if data.len == 0 { + return error('TraefikServer with name: traefik does not exist, prob bug.') + } + mut obj := json.decode(TraefikServer, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('traefik', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("TraefikServer with name 'traefik' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return traefik_global[args.name] or { - println(traefik_global) - // bug if we get here because should be in globals - panic('could not get config for traefik with name, is bug:${args.name}') + return error('could not get config for traefik with name:traefik') } } // register the config for the future pub fn set(o TraefikServer) ! { - set_in_mem(o)! + mut o2 := set_in_mem(o)! + traefik_default = o2.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('traefik', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:traefik', o2.name, json.encode(o2))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('traefik', args.name) + mut r := context.redis()! + return r.hexists('context:traefik', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('traefik', args.name)! - if args.name in traefik_global { - // del traefik_global[args.name] + mut r := context.redis()! + r.hdel('context:traefik', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&TraefikServer { + mut res := []&TraefikServer{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + traefik_global = map[string]&TraefikServer{} + traefik_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:traefik')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in traefik_global { + res << client + } + } + return res } // only sets in mem, does not set as config -fn set_in_mem(o TraefikServer) ! { +fn set_in_mem(o TraefikServer) !TraefikServer { mut o2 := obj_init(o)! - traefik_global[o.name] = &o2 - traefik_default = o.name + traefik_global[o2.name] = &o2 + traefik_default = o2.name + return o2 } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'traefik.') { + return + } mut install_actions := plbook.find(filter: 'traefik.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +130,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'traefik.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,36 +170,38 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } // load from disk and make sure is properly intialized pub fn (mut self TraefikServer) reload() ! { - switch(self.name) self = obj_init(self)! } pub fn (mut self TraefikServer) start() ! { - switch(self.name) if self.running()! { return } @@ -223,10 +264,12 @@ pub fn (mut self TraefikServer) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -253,12 +296,4 @@ pub fn (mut self TraefikServer) destroy() ! { // switch instance to be used for traefik pub fn switch(name string) { - traefik_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/web/zola/zola_factory_.v b/lib/installers/web/zola/zola_factory_.v index 90637c52..6203cd38 100644 --- a/lib/installers/web/zola/zola_factory_.v +++ b/lib/installers/web/zola/zola_factory_.v @@ -2,8 +2,8 @@ module zola import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit __global ( zola_global map[string]&ZolaInstaller @@ -15,14 +15,25 @@ __global ( @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' } -pub fn get(args_ ArgsGet) !&ZolaInstaller { +pub fn new(args ArgsGet) !&ZolaInstaller { return &ZolaInstaller{} } +pub fn get(args ArgsGet) !&ZolaInstaller { + return new(args)! +} + pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'zola.') { + return + } + mut install_actions := plbook.find(filter: 'zola.configure')! + if install_actions.len > 0 { + return error("can't configure zola, because no configuration allowed for this installer.") + } mut other_actions := plbook.find(filter: 'zola.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -44,28 +55,6 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { - // unknown - // screen - // zinit - // tmux - // systemd - match cat { - .zinit { - console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! - } - .systemd { - console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! - } - else { - console.print_debug('startupmanager: auto') - return startupmanager.get()! - } - } -} - @[params] pub struct InstallArgs { pub mut: @@ -93,10 +82,3 @@ pub fn (mut self ZolaInstaller) destroy() ! { pub fn switch(name string) { zola_default = name } - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' -} diff --git a/lib/lang/codewalker/README.md b/lib/lang/codewalker/README.md new file mode 100644 index 00000000..7fe6e3f9 --- /dev/null +++ b/lib/lang/codewalker/README.md @@ -0,0 +1,64 @@ +# CodeWalker Module + +The CodeWalker module provides functionality to walk through directories and create a map of files with their content. It's particularly useful for processing code directories while respecting gitignore patterns. + +## Features + +- Walk through directories recursively +- Respect gitignore patterns to exclude files +- Store file content in memory +- Export files back to a directory structure + +## Usage + +```v +import freeflowuniverse.herolib.lib.lang.codewalker + +mut cw := codewalker.new('/tmp/adir')! + +// Get content of a specific file +content := cw.filemap.get('path/to/file.txt')! + +// return output again +cw.filemap.content() + +// Export all files to a destination directory +cw.filemap.export('/tmp/exported_files')! + +``` + +### format of filemap + +## full files + +``` + +text before will be ignored + +===FILE:filename=== +code +===FILE:filename=== +code +===END=== + +text behind will be ignored + +``` + +## files with changes + +``` + +text before will be ignored + +===FILECHANGE:filename=== +code +===FILECHANGE:filename=== +code +===END=== + +text behind will be ignored + +``` + +FILECHANGE and FILE can be mixed, in FILE it means we have full content otherwise only changed content e.g. a method or s struct and then we need to use morph to change it \ No newline at end of file diff --git a/lib/lang/codewalker/codewalker.v b/lib/lang/codewalker/codewalker.v new file mode 100644 index 00000000..6069eb28 --- /dev/null +++ b/lib/lang/codewalker/codewalker.v @@ -0,0 +1,193 @@ +module codewalker + +import freeflowuniverse.herolib.core.pathlib + +pub struct CodeWalker { +pub mut: + gitignore_patterns []string + errors []CWError +} + +fn (cw CodeWalker) default_gitignore() []string { + return [ + '__pycache__/', + '*.py[cod]', + '*\$py.class', + '*.so', + '.Python', + 'build/', + 'develop-eggs/', + 'dist/', + 'downloads/', + 'eggs/', + '.eggs/', + 'lib/', + 'lib64/', + 'parts/', + 'sdist/', + 'var/', + 'wheels/', + '*.egg-info/', + '.installed.cfg', + '*.egg', + '.env', + '.venv', + 'venv/', + '.tox/', + '.nox/', + '.coverage', + '.coveragerc', + 'coverage.xml', + '*.cover', + '*.gem', + '*.pyc', + '.cache', + '.pytest_cache/', + '.mypy_cache/', + '.hypothesis/', + ] +} + + +@[params] +pub struct FileMapArgs{ +pub mut: + path string + content string +} + +pub fn (mut cw CodeWalker) filemap_get(args FileMapArgs) !FileMap { + if args.path != '' { + return cw.filemap_get_from_path(args.path)! + } else if args.content != '' { + return cw.filemap_get_from_content(args.content)! + } else { + return error('Either path or content must be provided to get FileMap') + } +} + +//get the filemap from a path +fn (mut cw CodeWalker) filemap_get_from_path(path string) !FileMap { + mut dir := pathlib.get(path) + if !dir.exists() { + return error('Source directory "${path}" does not exist') + } + + mut files := dir.list(recursive: true)! + mut fm := FileMap{ + source: path + } + + for mut file in files.paths { + if file.is_file() { + // Check if file should be ignored + relpath := file.path_relative(path)! + mut should_ignore := false + + for pattern in cw.gitignore_patterns { + if relpath.contains(pattern.trim_right('/')) || + (pattern.ends_with('/') && relpath.starts_with(pattern)) { + should_ignore = true + break + } + } + if !should_ignore { + content := file.read()! + fm.content[relpath] = content + } + } + } + return fm +} + +fn (mut cw CodeWalker) error(msg string,linenr int,category string, fail bool) ! { + cw.errors << CWError{ + message: msg + linenr: linenr + category: category + } + if fail { + mut errormsg:= "" + for e in cw.errors { + errormsg += "${e.message} (line ${e.linenr}, category: ${e.category})\n" + } + return error(msg) + } +} + +//internal function to get the filename +fn (mut cw CodeWalker) parse_filename_get(line string,linenr int) !string { + parts := line.split('===') + if parts.len < 2 { + cw.error("Invalid filename line: ${line}.",linenr, "filename_get", true)! + } + mut name:=parts[1].trim_space() + if name.len<2 { + cw.error("Invalid filename, < 2 chars: ${name}.",linenr, "filename_get", true)! + } + return name +} + +enum ParseState { + start + in_block +} + +//get file, is the parser +fn (mut cw CodeWalker) filemap_get_from_content(content string) !FileMap { + mut fm := FileMap{} + + mut filename := "" + mut block := []string{} + mut state := ParseState.start + mut linenr := 0 + + for line in content.split_into_lines() { + mut line2 := line.trim_space() + linenr += 1 + + match state { + .start { + if line2.starts_with('===FILE') && !line2.ends_with('===') { + filename = cw.parse_filename_get(line2, linenr)! + if filename == "END" { + cw.error("END found at start, not good.", linenr, "parse", true)! + return error("END found at start, not good.") + } + state = .in_block + } else if line2.len > 0 { + cw.error("Unexpected content before first file block: '${line}'.", linenr, "parse", false)! + } + } + .in_block { + if line2.starts_with('===FILE') { + if line2 == '===END===' { + fm.content[filename] = block.join_lines() + filename = "" + block = []string{} + state = .start + } else if line2.ends_with('===') { + fm.content[filename] = block.join_lines() + filename = cw.parse_filename_get(line2, linenr)! + if filename == "END" { + cw.error("Filename 'END' is reserved.", linenr, "parse", true)! + return error("Filename 'END' is reserved.") + } + block = []string{} + state = .in_block + } else { + block << line + } + } else { + block << line + } + } + } + } + + if state == .in_block && filename != '' { + fm.content[filename] = block.join_lines() + } + + return fm +} \ No newline at end of file diff --git a/lib/lang/codewalker/codewalker_test.v b/lib/lang/codewalker/codewalker_test.v new file mode 100644 index 00000000..0072cad1 --- /dev/null +++ b/lib/lang/codewalker/codewalker_test.v @@ -0,0 +1,128 @@ +module codewalker + +import os +import freeflowuniverse.herolib.core.pathlib + +fn test_parse_basic() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\nline1\nline2\n===END===' + fm := cw.parse(test_content)! + assert fm.content.len == 1 + assert fm.content['file1.txt'] == 'line1\nline2' +} + +fn test_parse_multiple_files() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\nline1\n===file2.txt===\nlineA\nlineB\n===END===' + fm := cw.parse(test_content)! + assert fm.content.len == 2 + assert fm.content['file1.txt'] == 'line1' + assert fm.content['file2.txt'] == 'lineA\nlineB' +} + +fn test_parse_empty_file_block() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===empty.txt===\n===END===' + fm := cw.parse(test_content)! + assert fm.content.len == 1 + assert fm.content['empty.txt'] == '' +} + +fn test_parse_consecutive_end_and_file() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\ncontent1\n===END===\n===file2.txt===\ncontent2\n===END===' + fm := cw.parse(test_content)! + assert fm.content.len == 2 + assert fm.content['file1.txt'] == 'content1' + assert fm.content['file2.txt'] == 'content2' +} + +fn test_parse_content_before_first_file_block() { + mut cw := new(CodeWalkerArgs{})! + test_content := 'unexpected content\n===file1.txt===\ncontent\n===END===' + // This should ideally log an error but still parse the file + fm := cw.parse(test_content)! + assert fm.content.len == 1 + assert fm.content['file1.txt'] == 'content' + assert cw.errors.len > 0 + assert cw.errors[0].message.contains('Unexpected content before first file block') +} + +fn test_parse_content_after_end() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\ncontent\n===END===\nmore unexpected content' + // This should ideally log an error but still parse the file up to END + fm := cw.parse(test_content)! + assert fm.content.len == 1 + assert fm.content['file1.txt'] == 'content' + assert cw.errors.len > 0 + assert cw.errors[0].message.contains('Unexpected content after ===END===') +} + +fn test_parse_invalid_filename_line() { + mut cw := new(CodeWalkerArgs{})! + test_content := '=== ===\ncontent\n===END===' + res := cw.parse(test_content) + if res is error { + assert res.msg.contains('Invalid filename, < 2 chars') + } else { + assert false // Should have errored + } +} + +fn test_parse_file_ending_without_end() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\nline1\nline2' + fm := cw.parse(test_content)! + assert fm.content.len == 1 + assert fm.content['file1.txt'] == 'line1\nline2' +} + +fn test_parse_empty_content() { + mut cw := new(CodeWalkerArgs{})! + test_content := '' + fm := cw.parse(test_content)! + assert fm.content.len == 0 +} + +fn test_parse_only_end_at_start() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===END===' + res := cw.parse(test_content) + if res is error { + assert res.msg.contains('END found at start, not good.') + } else { + assert false // Should have errored + } +} + +fn test_parse_empty_block_between_files() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\ncontent1\n===file2.txt===\n===END===\n===file3.txt===\ncontent3\n===END===' + fm := cw.parse(test_content)! + assert fm.content.len == 3 + assert fm.content['file1.txt'] == 'content1' + assert fm.content['file2.txt'] == '' + assert fm.content['file3.txt'] == 'content3' +} + +fn test_parse_multiple_empty_blocks() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\n===END===\n===file2.txt===\n===END===\n===file3.txt===\ncontent3\n===END===' + fm := cw.parse(test_content)! + assert fm.content.len == 3 + assert fm.content['file1.txt'] == '' + assert fm.content['file2.txt'] == '' + assert fm.content['file3.txt'] == 'content3' +} + +fn test_parse_filename_end_reserved() { + mut cw := new(CodeWalkerArgs{})! + test_content := '===file1.txt===\ncontent1\n===END===\n===END===\ncontent2\n===END===' + res := cw.parse(test_content) + if res is error { + assert res.msg.contains('Filename \'END\' is reserved.') + } else { + assert false // Should have errored + } +} \ No newline at end of file diff --git a/lib/lang/codewalker/factory.v b/lib/lang/codewalker/factory.v new file mode 100644 index 00000000..333054fe --- /dev/null +++ b/lib/lang/codewalker/factory.v @@ -0,0 +1,19 @@ +module codewalker + + +@[params] +pub struct CodeWalkerArgs { + source string //content we will send to an LLM, starting from a dir + content string //content as returned from LLM +} + +pub fn new(args CodeWalkerArgs) !CodeWalker { + mut cw := CodeWalker{ + source: args.source + } + + // Load default gitignore patterns + cw.gitignore_patterns = cw.default_gitignore() + + return cw +} diff --git a/lib/lang/codewalker/filemap.v b/lib/lang/codewalker/filemap.v new file mode 100644 index 00000000..70662e0d --- /dev/null +++ b/lib/lang/codewalker/filemap.v @@ -0,0 +1,83 @@ +module codewalker + +import freeflowuniverse.herolib.core.pathlib + +pub struct FileMap { +pub mut: + source string + content map[string]string + content_change map[string]string + errors []FMError +} + +pub fn (mut fm FileMap) content()string { + mut out:= []string{} + for filepath, filecontent in fm.content { + out << '===FILE:${filepath}===' + out << filecontent + } + for filepath, filecontent in fm.content_change { + out << '===FILECHANGE:${filepath}===' + out << filecontent + } + out << '===END===' + return out.join_lines() + +} + + +//write in new location, all will be overwritten, will only work with full files, not chanages +pub fn (mut fm FileMap) export(path string)! { + for filepath, filecontent in fm.content { + dest := "${fm.source}/${filepath}" + mut filepathtowrite := pathlib.get_file(path:dest,create:true)! + filepathtowrite.write(filecontent)! + } + +} + +@[PARAMS] +pub struct WriteParams { + path string + v_test bool = true + v_format bool = true + python_test bool +} + +//update the files as found in the folder and update them or create +pub fn (mut fm FileMap) write(path string)! { + for filepath, filecontent in fm.content { + dest := "${fm.source}/${filepath}" + //TODO check ends with .v or .py if v_test or python_test active then call python + //or v to check format of the file so we don't write broken code + //we first write in a temporary location $filename__.v and then test + //if good then overwrite $filename.v + mut filepathtowrite := pathlib.get_file(path:dest,create:true)! + filepathtowrite.write(filecontent)! + } + //TODO: phase 2, work with morphe to integrate change in the file +} + + + +pub fn (fm FileMap) get(relpath string) !string { + return fm.content[relpath] or { return error('File not found: ${relpath}') } +} + +pub fn (mut fm FileMap) set(relpath string, content string) { + fm.content[relpath] = content +} + +pub fn (mut fm FileMap) delete(relpath string) { + fm.content.delete(relpath) +} + +pub fn (fm FileMap) find(path string) []string { + mut result := []string{} + for filepath, _ in fm.content { + if filepath.starts_with(path) { + result << filepath + } + } + return result +} \ No newline at end of file diff --git a/lib/lang/codewalker/model.v b/lib/lang/codewalker/model.v new file mode 100644 index 00000000..057602af --- /dev/null +++ b/lib/lang/codewalker/model.v @@ -0,0 +1,16 @@ +module codewalker + +pub struct CWError { +pub: + message string + linenr int + category string +} + +pub struct FMError { +pub: + message string + linenr int //is optional + category string + filename string +} \ No newline at end of file diff --git a/lib/lang/python/templates/openrpc/env.sh b/lib/lang/python/templates/openrpc/env.sh new file mode 100755 index 00000000..dc9914ad --- /dev/null +++ b/lib/lang/python/templates/openrpc/env.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +cd "$(dirname "$0")" + +# Check if uv is installed +if ! command -v uv &> /dev/null; then + echo "❌ uv is not installed. Please install uv first:" + echo " curl -LsSf https://astral.sh/uv/install.sh | sh" + echo " or visit: https://docs.astral.sh/uv/getting-started/installation/" + exit 1 +fi + +echo "✅ uv found: $(uv --version)" + +# Create virtual environment if it doesn't exist +if [ ! -d ".venv" ]; then + echo "📦 Creating Python virtual environment..." + uv venv + echo "✅ Virtual environment created" +else + echo "✅ Virtual environment already exists" +fi + +export PATH="$SCRIPT_DIR/.venv/bin:$PATH" + +# Activate virtual environment +echo "🔄 Activating virtual environment..." +source .venv/bin/activate + +echo "✅ Virtual environment activated" + +uv sync + diff --git a/lib/lang/python/templates/openrpc/main.py b/lib/lang/python/templates/openrpc/main.py new file mode 100644 index 00000000..2fe24d8b --- /dev/null +++ b/lib/lang/python/templates/openrpc/main.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +import os +import json +import signal +import asyncio +from typing import Union + +import uvicorn +from fastapi import FastAPI, Response, WebSocket, WebSocketDisconnect +from jsonrpcobjects.objects import ( + ErrorResponse, + Notification, + ParamsNotification, + ParamsRequest, + Request, + ResultResponse, +) +from openrpc import RPCServer + +# ---------- FastAPI + OpenRPC ---------- +app = FastAPI(title="Calculator JSON-RPC (HTTP + UDS)") +RequestType = Union[ParamsRequest, Request, ParamsNotification, Notification] +rpc = RPCServer(title="Calculator API", version="1.0.0") + +# Calculator methods +@rpc.method() +async def add(a: float, b: float) -> float: + return a + b + +@rpc.method() +async def subtract(a: float, b: float) -> float: + return a - b + +@rpc.method() +async def multiply(a: float, b: float) -> float: + return a * b + +@rpc.method() +async def divide(a: float, b: float) -> float: + if b == 0: + # Keep it simple; library turns this into a JSON-RPC error + raise ValueError("Division by zero") + return a / b + +# Expose the generated OpenRPC spec as REST (proxy to rpc.discover) +@app.get("/openrpc.json") +async def openrpc_json() -> Response: + req = '{"jsonrpc":"2.0","id":1,"method":"rpc.discover"}' + resp = await rpc.process_request_async(req) # JSON string + payload = json.loads(resp) # dict with "result" + return Response(content=json.dumps(payload["result"]), + media_type="application/json") + +# JSON-RPC over WebSocket +@app.websocket("/rpc") +async def ws_process_rpc(websocket: WebSocket) -> None: + await websocket.accept() + try: + async def _process_rpc(request: str) -> None: + json_rpc_response = await rpc.process_request_async(request) + if json_rpc_response is not None: + await websocket.send_text(json_rpc_response) + + while True: + data = await websocket.receive_text() + asyncio.create_task(_process_rpc(data)) + except WebSocketDisconnect: + await websocket.close() + +# JSON-RPC over HTTP POST +@app.post("/rpc", response_model=Union[ErrorResponse, ResultResponse, None]) +async def http_process_rpc(request: RequestType) -> Response: + json_rpc_response = await rpc.process_request_async(request.model_dump_json()) + return Response(content=json_rpc_response, media_type="application/json") + + +# ---------- Run BOTH: TCP:7766 and UDS:/tmp/server1 ---------- +async def serve_both(): + uds_path = "/tmp/server1" + + # Clean stale socket path (if previous run crashed) + try: + if os.path.exists(uds_path) and not os.path.isfile(uds_path): + os.unlink(uds_path) + except FileNotFoundError: + pass + + # Create two uvicorn servers sharing the same FastAPI app + tcp_config = uvicorn.Config(app=app, host="127.0.0.1", port=7766, log_level="info") + uds_config = uvicorn.Config(app=app, uds=uds_path, log_level="info") + + tcp_server = uvicorn.Server(tcp_config) + uds_server = uvicorn.Server(uds_config) + + # We'll handle signals ourselves (avoid conflicts between two servers) + tcp_server.install_signal_handlers = False + uds_server.install_signal_handlers = False + + loop = asyncio.get_running_loop() + def _graceful_shutdown(): + tcp_server.should_exit = True + uds_server.should_exit = True + + for sig in (signal.SIGINT, signal.SIGTERM): + try: + loop.add_signal_handler(sig, _graceful_shutdown) + except NotImplementedError: + # e.g., on Windows; best-effort + pass + + try: + await asyncio.gather( + tcp_server.serve(), + uds_server.serve(), + ) + finally: + # Cleanup the socket file on exit + try: + if os.path.exists(uds_path) and not os.path.isfile(uds_path): + os.unlink(uds_path) + except Exception: + pass + + +if __name__ == "__main__": + asyncio.run(serve_both()) diff --git a/lib/lang/python/templates/openrpc/methods.py b/lib/lang/python/templates/openrpc/methods.py new file mode 100644 index 00000000..dd26b1af --- /dev/null +++ b/lib/lang/python/templates/openrpc/methods.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import os +import json +import signal +import asyncio +from typing import Union + +import uvicorn +from fastapi import FastAPI, Response, WebSocket, WebSocketDisconnect +from jsonrpcobjects.objects import ( + ErrorResponse, + Notification, + ParamsNotification, + ParamsRequest, + Request, + ResultResponse, +) +from openrpc import RPCServer + +# Calculator methods +@rpc.method() +async def add(a: float, b: float) -> float: + return a + b + +@rpc.method() +async def subtract(a: float, b: float) -> float: + return a - b + +@rpc.method() +async def multiply(a: float, b: float) -> float: + return a * b + +@rpc.method() +async def divide(a: float, b: float) -> float: + if b == 0: + # Keep it simple; library turns this into a JSON-RPC error + raise ValueError("Division by zero") + return a / b diff --git a/lib/lang/python/templates/openrpc/pyproject.toml b/lib/lang/python/templates/openrpc/pyproject.toml new file mode 100644 index 00000000..27487f5e --- /dev/null +++ b/lib/lang/python/templates/openrpc/pyproject.toml @@ -0,0 +1,28 @@ +[project] +name = "openrpc-server-1" +version = "0.1.0" +description = "Example openrpc server" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "fastapi>=0.104.0", + "uvicorn[standard]>=0.24.0", + "pydantic>=2.5.0", + "httpx>=0.25.0", + "fastapi-mcp>=0.1.0", + "pydantic-settings>=2.1.0", + "python-multipart>=0.0.6", + "jinja2>=3.1.2", + "click>=8.1.0", + "openrpc>=10.4.0" +] + + +[tool.uv] +dev-dependencies = [ + "pytest>=7.4.0", + "pytest-asyncio>=0.21.0", + "black>=23.0.0", + "isort>=5.12.0", + "mypy>=1.7.0", +] diff --git a/lib/lang/python/templates/openrpc/readme.md b/lib/lang/python/templates/openrpc/readme.md new file mode 100644 index 00000000..e69de29b diff --git a/lib/lang/python/templates/openrpc/start.sh b/lib/lang/python/templates/openrpc/start.sh new file mode 100755 index 00000000..d880d8c7 --- /dev/null +++ b/lib/lang/python/templates/openrpc/start.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e # Exit on any error + +cd "$(dirname "$0")" + +source env.sh + +python main.py \ No newline at end of file diff --git a/lib/lang/python/templates/openrpc/test.sh b/lib/lang/python/templates/openrpc/test.sh new file mode 100755 index 00000000..9deea222 --- /dev/null +++ b/lib/lang/python/templates/openrpc/test.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +HTTP_URL="http://127.0.0.1:7766/rpc" +HTTP_SPEC="http://127.0.0.1:7766/openrpc.json" +UDS_PATH="/tmp/server1" +UDS_URL="http://nothing/rpc" +UDS_SPEC="http://nothing/openrpc.json" + +fail() { + echo "❌ Test failed: $1" + exit 1 +} + +echo "🔎 Testing HTTP endpoint..." +resp_http=$(curl -s -H 'content-type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"add","params":{"a":2,"b":3}}' \ + "$HTTP_URL") + +val_http=$(echo "$resp_http" | jq -r '.result') +[[ "$val_http" == "5.0" ]] || fail "HTTP add(2,3) expected 5, got '$val_http'" + +echo "✅ HTTP add works" + +spec_http=$(curl -s "$HTTP_SPEC" | jq -r '.openrpc') +[[ "$spec_http" =~ ^1\..* ]] || fail "HTTP spec invalid" +echo "✅ HTTP spec available" + +echo "🔎 Testing UDS endpoint..." +resp_uds=$(curl -s --unix-socket "$UDS_PATH" \ + -H 'content-type: application/json' \ + -d '{"jsonrpc":"2.0","id":2,"method":"add","params":{"a":10,"b":4}}' \ + "$UDS_URL") + +val_uds=$(echo "$resp_uds" | jq -r '.result') +[[ "$val_uds" == "14.0" ]] || fail "UDS add(10,4) expected 14, got '$val_uds'" + +echo "✅ UDS add works" + +spec_uds=$(curl -s --unix-socket "$UDS_PATH" "$UDS_SPEC" | jq -r '.openrpc') +[[ "$spec_uds" =~ ^1\..* ]] || fail "UDS spec invalid" +echo "✅ UDS spec available" + +echo "🎉 All tests passed successfully" diff --git a/lib/mcp/rhai/logic/prompts/example_script.md b/lib/mcp/rhai/logic/prompts/example_script.md index 777fe75a..e67422ba 100644 --- a/lib/mcp/rhai/logic/prompts/example_script.md +++ b/lib/mcp/rhai/logic/prompts/example_script.md @@ -22,7 +22,7 @@ if repos.len() > 0 { if repo_array.len() > 0 { let repo = repo_array[0]; - print("\nRepository path: " + get_repo_path(repo)); + print("\nRepository path: " + path(repo)); // Check if the repository has changes let has_changes = has_changes(repo); diff --git a/lib/mycojobs/model/actor.v b/lib/mycojobs/model/actor.v new file mode 100644 index 00000000..368452a0 --- /dev/null +++ b/lib/mycojobs/model/actor.v @@ -0,0 +1,18 @@ +module model + +// a actor is a participant in the new internet, the one who can ask for work +// user can have more than one actor operating for them, an actor always operates in a context which is hosted by the hero of the user +// stored in the context db at actor: (actor is hset) +@[heap] +pub struct Actor { +pub mut: + id u32 + pubkey string + address []Address // address (is to reach the actor back), normally mycelium but doesn't have to be + created_at u32 // epoch + updated_at u32 // epoch +} + +pub fn (self Actor) redis_key() string { + return 'actor:${self.id}' +} diff --git a/lib/mycojobs/model/context.v b/lib/mycojobs/model/context.v new file mode 100644 index 00000000..69db32c7 --- /dev/null +++ b/lib/mycojobs/model/context.v @@ -0,0 +1,20 @@ +module model + +// each job is run in a context, this corresponds to a DB in redis and has specific rights to actors +// context is a redis db and also a locaction on a filesystem which can be used for e.g. logs, temporary files, etc. +// actors create contexts for others to work in +// stored in the context db at context: (context is hset) +@[heap] +pub struct Context { +pub mut: + id u32 // corresponds with the redis db (in our ourdb or other redis) + admins []u32 // actors which have admin rights on this context (means can do everything) + readers []u32 // actors which can read the context info + executors []u32 // actors which can execute jobs in this context + created_at u32 // epoch + updated_at u32 // epoch +} + +pub fn (self Context) redis_key() string { + return 'context:${self.id}' +} diff --git a/lib/mycojobs/model/flow.v b/lib/mycojobs/model/flow.v new file mode 100644 index 00000000..abc7c46d --- /dev/null +++ b/lib/mycojobs/model/flow.v @@ -0,0 +1,41 @@ +module model + +// what get's executed by an actor and needs to be tracked as a whole, can be represented as a DAG graph +// this is the high level representation of a workflow to execute on work, its fully decentralized and distributed +// only the actor who created the flow can modify it and holds it in DB +// stored in the context db at flow: (flow is hset) +@[heap] +pub struct Flow { +pub mut: + id u32 // this job id is given by the actor who called for it + caller_id u32 // is the actor which called for this job + context_id u32 // each job is executed in a context + jobs []u32 // links to all jobs which make up this flow, this can be dynamically modified + env_vars map[string]string // they are copied to every job done + result map[string]string // the result of the flow + created_at u32 // epoch + updated_at u32 // epoch + status FlowStatus +} + +pub fn (self Flow) redis_key() string { + return 'flow:${self.id}' +} + +// FlowStatus represents the status of a flow +pub enum FlowStatus { + dispatched + started + error + finished +} + +// str returns the string representation of FlowStatus +pub fn (self FlowStatus) str() string { + return match self { + .dispatched { 'dispatched' } + .started { 'started' } + .error { 'error' } + .finished { 'finished' } + } +} diff --git a/lib/mycojobs/model/message.v b/lib/mycojobs/model/message.v new file mode 100644 index 00000000..f0950769 --- /dev/null +++ b/lib/mycojobs/model/message.v @@ -0,0 +1,68 @@ +module model + +// Messages is what goes over mycelium (which is our messaging system), they can have a job inside +// stored in the context db at msg:: (msg is hset) +// there are 2 queues in the context db: queue: msg_out and msg_in these are generic queues which get all messages from mycelium (in) and the ones who need to be sent (out) are in the outqueue +@[heap] +pub struct Message { +pub mut: + id u32 // is unique id for the message, has been given by the caller + caller_id u32 // is the actor whos send this message + context_id u32 // each message is for a specific context + message string + message_type ScriptType + message_format_type MessageFormatType + timeout u32 // in sec, to arrive destination + timeout_ack u32 // in sec, to acknowledge receipt + timeout_result u32 // in sec, to process result and have it back + job []Job + logs []Log // e.g. for streaming logs back to originator + created_at u32 // epoch + updated_at u32 // epoch + status MessageStatus +} + +// MessageType represents the type of message +pub enum MessageType { + job + chat + mail +} + +// MessageFormatType represents the format of a message +pub enum MessageFormatType { + html + text + md +} + +pub fn (self Message) redis_key() string { + return 'message:${self.caller_id}:${self.id}' +} + +// queue_suffix returns the queue suffix for the message type +pub fn (mt MessageType) queue_suffix() string { + return match mt { + .job { 'job' } + .chat { 'chat' } + .mail { 'mail' } + } +} + +// MessageStatus represents the status of a message +pub enum MessageStatus { + dispatched + acknowledged + error + processed // e.g. can be something which comes back +} + +// str returns the string representation of MessageStatus +pub fn (ms MessageStatus) str() string { + return match ms { + .dispatched { 'dispatched' } + .acknowledged { 'acknowledged' } + .error { 'error' } + .processed { 'processed' } + } +} diff --git a/lib/mycojobs/model/runner.v b/lib/mycojobs/model/runner.v new file mode 100644 index 00000000..45cfe414 --- /dev/null +++ b/lib/mycojobs/model/runner.v @@ -0,0 +1,27 @@ +module model + +// a runner executes a job, this can be in VM, in a container or just some processes running somewhere +// the messages always come in over a topic +// stored in the context db at runner: (runner is hset) +@[heap] +pub struct Runner { +pub mut: + id u32 + pubkey string // from mycelium + address string // mycelium address + topic string // needs to be set by the runner but often runner e.g. runner20 + local bool // if local then goes on redis using the id + created_at u32 // epoch + updated_at u32 // epoch +} + +pub enum RunnerType { + v + python + osis + rust +} + +pub fn (self Runner) redis_key() string { + return 'runner:${self.id}' +} diff --git a/lib/mycojobs/model/runnerjob.v b/lib/mycojobs/model/runnerjob.v new file mode 100644 index 00000000..2ef789ed --- /dev/null +++ b/lib/mycojobs/model/runnerjob.v @@ -0,0 +1,64 @@ +module model + +// Job represents a job, a job is only usable in the context of a runner (which is part of a hero) +// stored in the context db at job:: (job is hset) +@[heap] +pub struct RunnerJob { +pub mut: + id u32 // this job id is given by the actor who called for it + caller_id u32 // is the actor which called for this job + context_id u32 // each job is executed in a context + script string + script_type ScriptType + timeout u32 // in sec + retries u8 + env_vars map[string]string + result map[string]string + prerequisites []string + dependends []u32 + created_at u32 // epoch + updated_at u32 // epoch + status JobStatus +} + +// ScriptType represents the type of script +pub enum ScriptType { + osis + sal + v + python +} + +pub fn (self RunnerJob) redis_key() string { + return 'job:${self.caller_id}:${self.id}' +} + +// queue_suffix returns the queue suffix for the script type +pub fn (st ScriptType) queue_suffix() string { + return match st { + .osis { 'osis' } + .sal { 'sal' } + .v { 'v' } + .python { 'python' } + } +} + +// JobStatus represents the status of a job +pub enum JobStatus { + dispatched + waiting_for_prerequisites + started + error + finished +} + +// str returns the string representation of JobStatus +pub fn (js JobStatus) str() string { + return match js { + .dispatched { 'dispatched' } + .waiting_for_prerequisites { 'waiting_for_prerequisites' } + .started { 'started' } + .error { 'error' } + .finished { 'finished' } + } +} diff --git a/lib/mycojobs/specs/models.md b/lib/mycojobs/specs/models.md new file mode 100644 index 00000000..4124ba1e --- /dev/null +++ b/lib/mycojobs/specs/models.md @@ -0,0 +1,314 @@ +# Models Specification +*Freeflow Universe – mycojobs* + +This document gathers **all data‑models** that exist in the `lib/mycojobs/model/` package, together with a concise purpose description, field semantics, Redis storage layout and the role each model plays in the overall *decentralised workflow* architecture. + + +## Table of Contents +1. [Actor](#actor) +2. [Context](#context) +3. [Flow](#flow) +4. [Message](#message) +5. [Runner](#runner) +6. [RunnerJob](#runnerjob) +7. [Enums & Shared Types](#enums-shared-types) +8. [Key‑generation helpers](#key-generation-helpers) + +--- + +##
    1️⃣ `Actor` – Identity & entry‑point + +| Field | Type | Description | +|------|------|-------------| +| `id` | `u32` | Sequential identifier **unique per tenant**. Used as part of the Redis key `actor:`. | +| `pubkey` | `string` | Public key (Mycelium‑compatible) that authenticates the actor when it sends/receives messages. | +| `address` | `[]Address` | One or more reachable addresses (normally Mycelium topics) that other participants can use to contact the actor. | +| `created_at` | `u32` | Unix‑epoch time when the record was created. | +| `updated_at` | `u32` | Unix‑epoch time of the last mutation. | + +### Purpose +* An **Actor** is the *human‑or‑service* that **requests work**, receives results and can be an administrator of a **Context**. +* It is the *security principal* – every operation in a context is authorised against the actor’s ID and its public key signature. + +### Redis representation + +| Key | Example | Storage type | Fields | +|-----|---------|--------------|--------| +| `actor:${id}` | `actor:12` | **hash** (`HSET`) | `id`, `pubkey`, `address` (list), `created_at`, `updated_at` | + +--- + +## 2️⃣ `Context` – Tenant & permission container + +| Field | Type | Description | +|------|------|-------------| +| `id` | `u32` | Identifier that also selects the underlying **Redis DB** for this tenant. | +| `admins` | `[]u32` | Actor IDs that have **full control** (create/delete any object, manage permissions). | +| `readers` | `[]u32` | Actor IDs that may **read** any object in the context but cannot modify. | +| `executors` | `[]u32` | Actor IDs allowed to **run** `RunnerJob`s and update their status. | +| `created_at` | `u32` | Unix‑epoch of creation. | +| `updated_at` | `u32` | Unix‑epoch of last modification. | + +### Purpose +* A **Context** isolates a *tenant* – each tenant gets its own Redis database and a dedicated filesystem area (for logs, temporary files, …). +* It stores **permission lists** that the system consults before any operation (e.g., creating a `Flow`, enqueuing a `RunnerJob`). + +### Redis representation + +| Key | Example | Storage type | Fields | +|-----|---------|--------------|--------| +| `context:${id}` | `context:7` | **hash** | `id`, `admins`, `readers`, `executors`, `created_at`, `updated_at` | + +--- + +## 3️⃣ `Flow` – High‑level workflow (DAG) + +| Field | Type | Description | +|------|------|-------------| +| `id` | `u32` | Flow identifier – *unique inside the creator’s actor space*. | +| `caller_id` | `u32` | Actor that **created** the flow (owner). | +| `context_id` | `u32` | Context in which the flow lives. | +| `jobs` | `[]u32` | List of **RunnerJob** IDs that belong to this flow (the DAG edges are stored in each job’s `dependends`). | +| `env_vars` | `map[string]string` | Global environment variables injected into **every** job of the flow. | +| `result` | `map[string]string` | Aggregated output produced by the flow (filled by the orchestrator when the flow finishes). | +| `created_at` | `u32` | Creation timestamp. | +| `updated_at` | `u32` | Last update timestamp. | +| `status` | `FlowStatus` | Current lifecycle stage (`dispatched`, `started`, `error`, `finished`). | + +### Purpose +* A **Flow** is the *public‑facing* representation of a **workflow**. +* It groups many `RunnerJob`s, supplies common env‑vars, tracks overall status and collects the final result. +* Only the *creator* (the `caller_id`) may mutate the flow definition. + +### Redis representation + +| Key | Example | Storage type | Fields | +|-----|---------|--------------|--------| +| `flow:${id}` | `flow:33` | **hash** | `id`, `caller_id`, `context_id`, `jobs`, `env_vars`, `result`, `created_at`, `updated_at`, `status` | + +### `FlowStatus` enum + +| Value | Meaning | +|-------|---------| +| `dispatched` | Flow has been stored but not yet started. | +| `started` | At least one job is running. | +| `error` | One or more jobs failed; flow aborted. | +| `finished` | All jobs succeeded, `result` is final. | + +--- + +## 4️⃣ `Message` – Transport unit (Mycelium) + +| Field | Type | Description | +|------|------|-------------| +| `id` |u32 `_type` | `ScriptType` | *Kind* of the message – currently re‑used for job payloads (`osis`, `sal`, `v`, `python`). | +| `message_format_type` | `MessageFormatType` | Formatting of `message` (`html`, `text`, `md`). | +| `timeout` | `u32` | Seconds before the message is considered *lost* if not delivered. | +| `timeout_ack` | `u32` | Seconds allowed for the receiver to acknowledge. | +| `timeout_result` | `u32` | Seconds allowed for the receiver to send back a result. | +| `job` | `[]Job` | Embedded **RunnerJob** objects (normally a single job). | +| `logs` | `[]Log` | Optional streaming logs attached to the message. | +| `created_at` | `u32` | Timestamp of creation. | +| `updated_at` | `u32` | Timestamp of latest update. | +| `status` | `MessageStatus` | Current lifecycle (`dispatched`, `acknowledged`, `error`, `processed`). | + +### Purpose +* `Message` is the **payload carrier** that travels over **Mycelium** (the pub/sub system). +* It can be a **job request**, a **chat line**, an **email**, or any generic data that needs to be routed between actors, runners, or services. +* Every message is persisted as a Redis hash; the system also maintains two *generic* queues: + + * `msg_out` – outbound messages waiting to be handed to Mycelium. + * `msg_in` – inbound messages that have already arrived and are awaiting local processing. + +### Redis representation + +| Key | Example | Storage type | Fields | +|-----|---------|--------------|--------| +| `message:${caller_id}:${id}` | `message:12:101` | **hash** | All fields above (`id`, `caller_id`, `context_id`, …, `status`). | + +### `MessageType` enum (legacy – not used in current code but documented) + +| Value | Meaning | +|-------|---------| +| `job` | Payload carries a `RunnerJob`. | +| `chat` | Human‑to‑human communication. | +| `mail` | Email‑like message. | + +### `MessageFormatType` enum + +| Value | Meaning | +|-------|---------| +| `html` | HTML formatted body. | +| `text` | Plain‑text. | +| `md` | Markdown. | + +### `MessageStatus` enum + +| Value | Meaning | +|-------|---------| +| `dispatched` | Stored, not yet processed. | +| `acknowledged` | Receiver has confirmed receipt. | +| `error` | Delivery or processing failed. | +|` | Message handled (e.g., job result returned). | + +--- + +## 5️⃣ `Runner` – Worker that executes jobs + +| Field | Type | Description | +|------|------|-------------| +| `id` | `u32` | Unique runner identifier. | +| `pubkey` | `string` | Public key of the runner (used by Mycelium for auth). | +| `address` | `string` | Mycelium address (e.g., `mycelium://…`). | +| `topic` | `string` | Pub/Sub topic the runner subscribes to; defaults to `runner${id}`. | +| `local` | `bool` | If `true`, the runner also consumes jobs directly from **Redis queues** (e.g., `queue:v`). | +| `created_at` | `u32` | Creation timestamp. | +| `updated_at` | `u32` | Last modification timestamp. | + +### Purpose +* A **Runner** is the *execution engine* – it could be a VM, a container, or a process that knows how to run a specific script type (`v`, `python`, `osis`, `rust`). +* It **subscribes** to a Mycelium topic to receive job‑related messages, and, when `local==true`, it also **polls** a Redis list named after the script‑type (`queue:`). + +### Redis representation + +| Key | Example | Storage type | +|-----|---------|--------------| +| `runner:${id}` | `runner:20` | **hash** *(all fields above)* | + +### `RunnerType` enum + +| Value | Intended runtime | +|-------|------------------| +| `v` | V language VM | +| `python` | CPython / PyPy | +| `osis` | OSIS‑specific runtime | +| `rust` | Native Rust binary | + +--- + +## 6️⃣ `RunnerJob` – Executable unit + +| Field | Type | Description | +|------|------|-------------| +| `id` | `u32` | Job identifier **provided by the caller**. | +| `caller_id` | `u32` | Actor that created the job. | +| `context_id` | `u32` | Context in which the job will run. | +| `script` | `string` | Source code / command to be executed. | +| `script_type` | `ScriptType` | Language or runtime of the script (`osis`, `sal`, `v`, `python`). | +| `timeout` | `u32` | Maximum execution time (seconds). | +| `retries` | `u8` | Number of automatic retries on failure. | +| `env_vars` | `map[string]string` | Job‑specific environment variables (merged with `Flow.env_vars`). | +| `result` | `map[string]string` | Key‑value map that the job writes back upon completion. | +| `prerequisites` | `[]string` | Human‑readable IDs of **external** prerequisites (e.g., files, other services). | +| `dependends` | `[]u32` | IDs of **other RunnerJob** objects that must finish before this job can start. | +| `created_at` | `u32` | Creation timestamp. | +| `updated_at` | `u32` | Last update timestamp. | +| `status` | `JobStatus` | Lifecycle status (`dispatched`, `waiting_for_prerequisites`, `started`, `error`, `finished`). | + +### Purpose +* A **RunnerJob** is the *atomic piece of work* that a `Runner` executes. +* It lives inside a **Context**, is queued according to its `script_type`, and moves through a well‑defined **state machine**. +* The `dependends` field enables the *DAG* behaviour that the `Flow` model represents at a higher level. + +### Redis representation + +| Key | Example | Storage type | +|-----|---------|--------------| +| `job:${caller_id}:${id}` | `job:12:2001` | **hash** *(all fields above)* | + +### `ScriptType` enum + +| Value | Runtime | +|-------|---------| +| `osis` | OSIS interpreter | +| `sal` | SAL DSL (custom) | +| `v` | V language | +| `python`| CPython / PyPy | + +*The enum provides a **`queue_suffix()`** helper that maps a script type to the name of the Redis list used for local job dispatch (`queue:python`, `queue:v`, …).* + +### `JobStatus` enum + +| Value | Meaning | +|-------|---------| +| `dispatched` | Stored, waiting to be examined for prerequisites. | +| `waiting_for_prerequisites` | Has `dependends` that are not yet finished. | +| `started` | Currently executing on a runner. | +| `error` | Execution failed (or exceeded retries). | +| `finished` | Successfully completed, `result` populated. | + +--- + +## 7️⃣ Other Enums & Shared Types + +| Enum | Location | Values | Note | +|------|----------|--------|------| +| `MessageType` | `message.v` | `job`, `chat`, `mail` | Determines how a `Message` is interpreted. | +| `MessageFormatType` | `message.v` | `html`, `text`, `md` | UI‑layer rendering hint. | +| `MessageStatus` | `message.v` | `dispatched`, `acknowledged`, `error`, `processed` | Life‑cycle of a `Message`. | +| `FlowStatus` | `flow.v` | `dispatched`, `started`, `error`, `finished` | High‑level flow progress. | +| `RunnerType` | `runner.v` | `v`, `python`, `osis`, `rust` | Not currently stored; used by the orchestration layer to pick a runner implementation. | +| `ScriptType` | `runnerjob.v` | `osis`, `sal`, `v`, `python` | Determines queue suffix & runtime. | +| `JobStatus` | `runnerjob.v` | `dispatched`, `waiting_for_prerequisites`, `started`, `error`, `finished` | Per‑job state machine. | + +--- + +## 8️⃣ Key‑generation helpers (methods) + +| Model | Method | Returns | Example | +|-------|--------|---------|---------| +| `Actor` | `redis_key()` | `"actor:${self.id}"` | `actor:12` | +| `Context` | `redis_key()` | `"context:${self.id}"` | `context:7` | +| `Flow` | `redis_key()` | `"flow:${self.id}"` | `flow:33` | +| `Message` | `redis_key()` | `"message:${self.caller_id}:${self.id}"` | `message:12:101` | +| `Runner` | `redis_key()` | `"runner:${self.id}"` | `runner:20` | +| `RunnerJob` | `redis_key()` | `"job:${self.caller_id}:${self.id}"` | `job:12:2001` | +| `MessageType` | `queue_suffix()` | `"job"` / `"chat"` / `"mail"` | `MessageType.job.queue_suffix() → "job"` | +| `ScriptType` | `queue_suffix()` | `"osis"` / `"sal"` / `"v"` / `"python"` | `ScriptType.python.queue_suffix() → "python"` | + +These helpers guarantee **canonical key naming** throughout the code base and simplify Redis interactions. + +--- + +## 📌 Summary Diagram (quick reference) + +```mermaid +%%{init: {"theme":"dark"}}%% +graph TD + %% Actors and what they can create + A[Actor] -->|creates| Ctx[Context] + A -->|creates| Fl[Flow] + A -->|creates| Msg[Message] + A -->|creates| Rnr[Runner] + A -->|creates| Job[RunnerJob] + + %% All objects live inside one Redis DB that belongs to a Context + subgraph "Redis DB (per Context)" + Ctx + A + Fl + Msg + Rnr + Job + end + + %% Messaging queues (global, outside the Context DB) + Msg -->|pushes key onto| OutQ[msg_out] + OutQ -->|transport via Mycelium| InQ[msg_in] + InQ -->|pulled by| Rnr + + %% Local runner queues (only when runner.local == true) + Rnr -->|BRPOP from| QueueV["queue:v"] + Rnr -->|BRPOP from| QueuePy["queue:python"] + Rnr -->|BRPOP from| QueueOSIS["queue:osis"] + +``` + +## context based + +* Inside a Context, an **Actor** can create a **Flow** that references many **RunnerJob** IDs (the DAG). +* To *initiate* execution, the Actor packages a **RunnerJob** (or a full Flow) inside a **Message**, pushes it onto `msg_out`, and the system routes it via **Mycelium** to the target Context. +* The remote **Runner** receives the Message, materialises the **RunnerJob**, queues it on a script‑type list, executes it, writes back `result` and status, and optionally sends a *result Message* back to the originator. + +All state is persisted as **Redis hashes**, guaranteeing durability and enabling *idempotent* retries. The uniform naming conventions (`actor:`, `job::`, …) make it trivial to locate any object given its identifiers. + diff --git a/lib/mycojobs/specs/specs.md b/lib/mycojobs/specs/specs.md new file mode 100644 index 00000000..9e58f7d7 --- /dev/null +++ b/lib/mycojobs/specs/specs.md @@ -0,0 +1,263 @@ + +## Objects Used + +| Component | What it **stores** | Where it lives (Redis key) | Main responsibilities | +|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Actor** | Public key, reachable addresses, timestamps | `actor:` (hash) | An identity that can request work, receive results and act as an administrator of a *Context*. | +| **Context**| Permission lists (`admins`, `readers`, `executors`), timestamps | `context:` (hash) | An isolated “tenant” – a separate Redis DB and filesystem area. All objects (flows, messages, jobs, runners) belonging to a given workflow are stored under this context. The permission lists control who may read, execute or administer the context. | +| **Flow** | DAG of job IDs, env‑vars, result map, status, timestamps | `flow:` (hash) | A high‑level workflow created by a single **Actor**. It groups many **RunnerJob** objects, records their execution order, supplies common environment variables and aggregates the final result. | +| **Message**| Payload, type (`job\|chat\|mail`), format (`html\|text\|md`), time‑outs, embedded **Job** objects, log stream, status, timestamps | `message::` (hash) | The transport unit that travels over **Mycelium** (the pub/sub/message bus). A message can contain a **RunnerJob** (or a list of jobs) and is queued in two generic Redis lists: `msg_out` (to be sent) and `msg_in` (already received). | +| **Runner** | Public key, Mycelium address, topic name, type (`v\|python\|osis\|rust`), local flag, timestamps | `runner:` (hash) | The *worker* that actually executes **RunnerJob** scripts. It subscribes to a Mycelium topic (normally `runner`). If `local == true` the runner also consumes jobs directly from a Redis queue that is named after the script‑type suffix (`v`, `python`, …). | +| **RunnerJob**| Script source, type (`osis\|sal\|v\|python`), env‑vars, prerequisites, dependencies, status, timestamps, result map | `job::` (hash) | A single executable unit. It lives inside a **Context**, belongs to a **Runner**, and is queued according to its `script_type` (e.g. `queue:python`). Its status moves through the lifecycle `dispatched → waiting_for_prerequisites → started → finished|error`. | + +> **Key idea:** All objects are persisted as *hashes* in a **Redis** database that is dedicated to a *Context*. The system is completely **decentralised** – each actor owns its own context and can spin up as many runners as needed. Communication between actors, runners and the rest of the system happens over **Mycelium**, a message‑bus that uses Redis lists as queues. + +--- + +## Interaction diagram (who talks to who) + +### Sequence diagram – “Submit a flow and run it” + +```mermaid +%%{init: {"theme":"dark"}}%% +sequenceDiagram + participant A as Actor + participant L as Local‑Context (Redis) + participant M as Mycelium (msg_out / msg_in) + participant R as Remote‑Context (Redis) + participant W as Runner (worker) + + %% 1. Actor creates everything locally + A->>L: create Flow + RunnerJob (J) + A->>L: LPUSH msg_out Message{type=job, payload=J, target=Remote} + + %% 2. Mycelium transports the message + M->>R: LPUSH msg_in (Message key) + + %% 3. Remote context materialises the job + R->>R: HSET Message hash + R->>R: HSET RunnerJob (J') // copy of payload + R->>R: LPUSH queue:v (job key) + + %% 4. Runner consumes and executes + W->>R: BRPOP queue:v (job key) + W->>R: HSET job status = started + W->>W: execute script + W->>R: HSET job result + status = finished + + %% 5. Result is sent back + W->>M: LPUSH msg_out Message{type=result, payload=result, target=Local} + M->>L: LPUSH msg_in (result Message key) + + %% 6. Actor receives the result + A->>L: RPOP msg_in → read result +``` + +### 2.2 Component diagram – “Static view of objects & links” + +```mermaid +%%{init: {"theme":"dark"}}%% +graph LR + subgraph Redis["Redis (per Context)"] + A[Actor] -->|stores| Ctx[Context] + Ctx -->|stores| Fl[Flow] + Ctx -->|stores| Msg[Message] + Ctx -->|stores| Rnr[Runner] + Ctx -->|stores| Job[RunnerJob] + end + + subgraph Mycelium["Mycelium (Pub/Sub)"] + MsgOut["queue:msg_out"] -->|outgoing| Mcel[Mycelium Bus] + Mcel -->|incoming| MsgIn["queue:msg_in"] + RnrTopic["topic:runnerX"] -->|subscribed by| Rnr + queueV["queue:v"] -->|local jobs| Rnr + queuePython["queue:python"] -->|local jobs| Rnr + end + + A -->|creates / reads| Fl + A -->|creates / reads| Msg + A -->|creates / reads| Rnr + A -->|creates / reads| Job + Fl -->|references| Job + Msg -->|may embed| Job + Rnr -->|executes| Job + Job -->|updates| Fl + Msg -->|carries result back to| A +``` + +### 2.3 Flow‑status life‑cycle (state diagram) + +```mermaid +%%{init: {"theme":"dark"}}%% +stateDiagram-v2 + [*] --> dispatched + dispatched --> waiting_for_prerequisites : has prereqs + waiting_for_prerequisites --> started : prereqs met + dispatched --> started : no prereqs + started --> finished : success + started --> error : failure + waiting_for_prerequisites --> error : timeout / impossible + error --> [*] + finished --> [*] +``` + +--- + +## 3️⃣ Redis objects – concrete key & data layout + +All objects are stored as **hashes** (`HSET`). Below is a concise catalog that can be copied into a design doc. + +| Key pattern | Example | Fields (type) | Comments | +|-------------|---------|---------------|----------| +| `actor:${id}` | `actor:12` | `id` u32, `pubkey` str, `address` list\, `created_at` u32, `updated_at` u32 | One hash per actor. | +| `context:${id}` | `context:7` | `id` u32, `admins` list\, `readers` list\, `executors` list\, `created_at` u32, `updated_at` u32 | Holds permission lists for a tenant. | +| `flow:${id}` | `flow:33` | `id` u32, `caller_id` u32, `context_id` u32, `jobs` list\, `env_vars` map\, `result` map\, `created_at` u32, `updated_at` u32, `status` str (`dispatched|started|error|finished`) | +| `message:${caller_id}:${id}` | `message:12:101` | `id` u32, `caller_id` u32, `context_id` u32, `message` str, `message_type` str (`job|chat|mail`), `message_format_type` str (`html|text|md`), `timeout` u32, `timeout_ack` u32, `timeout_result` u32, `job` list\ (serialized), `logs` list\, `created_at` u32, `updated_at` u32, `status` str (`dispatched|acknowledged|error|processed`) | +| `runner:${id}` | `runner:20` | `id` u32, `pubkey` str, `address` str, `topic` str, `local` bool, `created_at` u32, `updated_at` u32 | +| `job:${caller_id}:${id}` | `job:12:2001` | `id` u32, `caller_id` u32, `context_id` u32, `script` str, `script_type` str (`osis|sal|v|python`), `timeout` u32, `retries` u8, `env_vars` map\, `result` map\, `prerequisites` list\, `dependends` list\, `created_at` u32, `updated_at` u32, `status` str (`dispatched|waiting_for_prerequisites|started|error|finished`) | + +#### Queue objects (lists) + +| Queue name | Purpose | +|------------|---------| +| `msg_out` | **Outbound** generic queue – every `Message` that an actor wants to send is pushed here. | +| `msg_in` | **Inbound** generic queue – every message received from Mycelium is placed here for the local consumer to process. | +| `queue:${suffix}` (e.g. `queue:v`, `queue:python`) | Local job queues used by a **Runner** when `local == true`. The suffix comes from `ScriptType.queue_suffix()`. | + +--- + +## 4️⃣ System specification (as a concise “specs” section) + +### 4.1 Naming conventions +* All Redis **hashes** are prefixed with the object name (`actor:`, `context:`, …). +* All **queues** are simple Redis lists (`LPUSH` / `RPOP`). +* **Message** keys embed both the *caller* and a locally unique *message id* – this guarantees global uniqueness across contexts. + +### 4.2 Permissions & security +* Only IDs present in `Context.admins` may **create** or **delete** any object inside that context. +* `Context.readers` can **GET** any hash but not modify it. +* `Context.executors` are allowed to **update** `RunnerJob.status`, `result` and to **pop** from local job queues. +* Every `Actor` must present a `pubkey` that can be verified by the receiving side (Mycelium uses asymmetric crypto). + + + +### 4.3 Message flow (publish / consume) + + + +Below is a **re‑written “Message flow (publish / consume)”** that reflects the real runtime components: + +* **Supervisor daemon** – runs on the node that owns the **Flow** (the *actor’s* side). + It is the only process that ever **RPOP**s from the global `msg_out` queue, adds the proper routing information and hands the message to **Mycelium**. + +* **Mycelium** – the pure pub/sub/message‑bus. It never touches Redis directly; it only receives a *payload key* from the coordinator and delivers that key to the remote tenant’s `msg_in` list. + +* **Remote‑side runner / service** – consumes from its own `msg_in`, materialises the job and executes it. + +The table now uses the exact component names and adds a short note about the permission check that the coordinator performs before it releases a message. + +| # | Action (what the system does) | Component that performs it | Redis interaction (exact commands) | +|---|-------------------------------|----------------------------|------------------------------------| +| **1️⃣ Publish** | Actor creates a `Message` hash and **LPUSH**es its key onto the *outbound* queue. | **Actor** (client code) | `HSET message:12:101 …`
    `LPUSH msg_out message:12:101` | +| **2️⃣ Coordinate & route** | The **Supervisor daemon** (running at source) **RPOP**s the key, checks the actor’s permissions, adds the *target‑context* and *topic* fields, then forwards the key to Mycelium. | **Supervisor daemon** (per‑actor) | `RPOP msg_out` → (in‑process) → `LPUSH msg_out_coordinator ` (internal buffer) | +| **3️⃣ Transport** | Mycelium receives the key, looks at `Message.message_type` (or the explicit `topic`) and pushes the key onto the *inbound* queue of the **remote** tenant. | **Mycelium bus** (network layer) | `LPUSH msg_in: ` | +| **4️⃣ Consume** | The **Remote side** (runner or service) **RPOP**s from its `msg_in`, loads the full hash, verifies the actor’s signature and decides what to do based on `message_type`. | **Remote consumer** (runner / service | `RPOP msg_in:` → `HGETALL message:` | +| **5️⃣ Job materialisation** | If `message_type == "job"` the consumer creates a **RunnerJob** entry inside the **remote** context, adds the job **key** to the proper *script‑type* queue (`queue:v`, `queue:python`, …). | **Remote consumer** | `HSET job:: …`
    `LPUSH queue: job::` | +| **6️⃣ Runner execution loop** | A **Runner** attached to that remote context **BRPOP**s from its script‑type queue, sets `status = started`, runs the script, writes `result` and final `status`. | **Runner** | `BRPOP queue:` → `HSET job:<…> status started` → … → `HSET job:<…> result … status finished` | +| **7️⃣ Result notification** | The runner builds a new `Message` (type `chat`, `result`, …) and pushes it onto **msg_out** again. The **Supervisor daemon** on the *originating* side will later pick it up and route it back to the original actor. | **Runner** → **Supervisor (remote side)** → **Mycelium** → **Supervisor (origin side)** → **Actor** | `HSET message: …`
    `LPUSH msg_out message:` (steps 2‑3 repeat in reverse direction) | + +--- + +## Tiny end‑to‑end sequence (still simple enough to render) + +```mermaid +%%{init: {"theme":"dark"}}%% +sequenceDiagram + participant A as Actor + participant L as Local‑Redis (Flow ctx) + participant C as Supervisor daemon (local) + participant M as Mycelium bus + participant R as Remote‑Redis (target ctx) + participant W as Runner (remote) + + %% 1️⃣ publish + A->>L: HSET message:12:101 … + A->>L: LPUSH msg_out message:12:101 + + %% 2️⃣ coordinate + C->>L: RPOP msg_out + C->>C: check permissions / add routing info + C->>M: push key to Mycelium (msg_out_coordinator) + + %% 3️⃣ transport + M->>R: LPUSH msg_in message:12:101 + + %% 4️⃣ consume + R->>W: RPOP msg_in + R->>R: HGETALL message:12:101 + R->>R: verify signature + alt message_type == job + R->>R: HSET job:12:2001 … + R->>R: LPUSH queue:v job:12:2001 + end + + %% 5️⃣ runner loop + W->>R: BRPOP queue:v (job:12:2001) + W->>R: HSET job:12:2001 status started + W->>W: execute script + W->>R: HSET job:12:2001 result … status finished + + %% 6️⃣ result back + W->>R: HSET message:12:900 result … + W->>R: LPUSH msg_out message:12:900 + C->>M: (coordinator on remote side) routes back + M->>L: LPUSH msg_in message:12:900 + A->>L: RPOP msg_in → read result +``` + + +## 5️⃣ What the **system** is trying to achieve + +| Goal | How it is realized | +|------|--------------------| +| **Decentralised execution** | Every *actor* owns a **Context**; any number of **Runners** can be attached to that context, possibly on different machines, and they all talk over the same Mycelium/Redis backend. | +| **Fine‑grained permissions** | `Context.admins/readers/executors` enforce who can create, view or run jobs. | +| **Loose coupling via messages** | All actions (job submission, result propagation, chat, mail …) use the generic `Message` object; the same transport pipeline handles all of them. | +| **Workflow orchestration** | The **Flow** object models a DAG of jobs, tracks collective status and aggregates results, without needing a central scheduler. | +| **Pluggable runtimes** | `ScriptType` and `RunnerType` let a runner choose the proper execution environment (V, Python, OSIS, Rust, …) – adding a new language only means adding a new `ScriptType` and a corresponding worker. | +| **Observability** | `Log` arrays attached to a `Message` and the timestamps on every hash give a complete audit trail. | +| **Resilience** | Jobs are idempotent hash entries; queues are persisted in Redis, and status changes are atomic (`HSET`). Retries and time‑outs guarantee eventual consistency. | + +--- + +## 6️⃣ Diagram summary (quick visual cheat‑sheet) + +```mermaid +%%{init: {"theme":"dark"}}%% +graph TD + A[Actor] -->|creates| Ctx[Context] + A -->|creates| Flow + A -->|creates| Msg + A -->|creates| Rnr[Runner] + A -->|creates| Job[RunnerJob] + + subgraph Redis["Redis (per Context)"] + Ctx --> A + Ctx --> Flow + Ctx --> Msg + Ctx --> Rnr + Ctx --> Job + end + + Msg -->|push to| OutQ[msg_out] + OutQ --> Myc[Mycelium Bus] + Myc -->|deliver| InQ[msg_in] + InQ --> Rnr + Rnr -->|pop from| Qv["queue:v"] + Rnr -->|pop from| Qpy["queue:python"] + + Rnr -->|updates| Job + Job -->|updates| Flow + Flow -->|result Message| Msg +``` + diff --git a/lib/osal/core/done.v b/lib/osal/core/done.v index e6b69933..cb646938 100644 --- a/lib/osal/core/done.v +++ b/lib/osal/core/done.v @@ -1,29 +1,27 @@ module core import freeflowuniverse.herolib.core.base -import freeflowuniverse.herolib.data.dbfs +import freeflowuniverse.herolib.core.redisclient import freeflowuniverse.herolib.ui.console -fn donedb() !&dbfs.DB { +fn donedb() !&redisclient.Redis { mut context := base.context()! - mut collection := context.dbcollection()! - mut db := collection.db_get_create(name: 'todo', withkeys: true)! - return &db + return context.redis()! } pub fn done_set(key string, val string) ! { mut db := donedb()! - db.set(key: key, value: val)! + db.hset('context:done', key, val)! } pub fn done_get(key string) ?string { mut db := donedb() or { panic(err) } - return db.get(key: key) or { return none } + return db.hget('context:done', key) or { return none } } pub fn done_delete(key string) ! { mut db := donedb()! - db.delete(key: key)! + db.hdel('context:done', key)! } pub fn done_get_str(key string) string { @@ -38,7 +36,7 @@ pub fn done_get_int(key string) int { pub fn done_exists(key string) bool { mut db := donedb() or { panic(err) } - return db.exists(key: key) or { false } + return db.hexists('context:done', key) or { false } } pub fn done_print() ! { @@ -54,5 +52,5 @@ pub fn done_print() ! { pub fn done_reset() ! { mut db := donedb()! - db.destroy()! + db.del('context:done')! } diff --git a/lib/osal/core/exec.v b/lib/osal/core/exec.v index 34653cbb..9c009334 100644 --- a/lib/osal/core/exec.v +++ b/lib/osal/core/exec.v @@ -1,6 +1,6 @@ module core -// import freeflowuniverse.herolib.core.texttools +import freeflowuniverse.herolib.core.texttools // import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.ui.console import json @@ -481,3 +481,61 @@ pub fn exec_string(cmd Command) !string { job.cmd.scriptpath = cmd_to_script_path(job.cmd)! return job.cmd.scriptpath } + +@[params] +pub struct CommandFast { +pub mut: + cmd string + ignore_error bool // means if error will just exit and not raise, there will be no error reporting + work_folder string // location where cmd will be executed + environment map[string]string // env variables + ignore_error_codes []int + debug bool // if debug will put +ex in the script which is being executed and will make sure script stays + includeprofile bool + notempty bool +} + +// execute something fast, make sure it returns an error if the result is empty +// source the +pub fn exec_fast(cmd_ CommandFast) !string { + mut cmd_str := texttools.dedent(cmd_.cmd) + + mut toexecute := []string{} + + if cmd_.debug { + // Add +ex for debug mode if it's a bash script + // This is a simplification, ideally we'd check if it's a bash script + // For now, assume it's a bash script if debug is true and prepend + toexecute << 'set -ex' + } + + if cmd_.includeprofile { + toexecute << profile_path_source_and()! + } + + for key, val in cmd_.environment { + toexecute << 'export ${key}=${val}' + } + + if cmd_.work_folder.len > 0 { + toexecute << 'cd ${cmd_.work_folder}' + } + + toexecute << cmd_str + + mut cmd_str2 := toexecute.join('\n') + + result := os.execute(cmd_str2) + + if result.exit_code != 0 { + if !(cmd_.ignore_error || result.exit_code in cmd_.ignore_error_codes) { + return error("couldn't execute '${cmd_.cmd}', result code: ${result.exit_code}\n${result.output}") + } + } + + if cmd_.notempty && result.output.len == 0 { + return error("couldn't execute '${cmd_.cmd}', the result is empty but notempty is true") + } + + return result.output +} diff --git a/lib/osal/screen/factory.v b/lib/osal/screen/factory.v index e8393e47..77429760 100644 --- a/lib/osal/screen/factory.v +++ b/lib/osal/screen/factory.v @@ -5,7 +5,8 @@ import freeflowuniverse.herolib.core.texttools // import freeflowuniverse.herolib.screen import os import time -import freeflowuniverse.herolib.ui.console +// import freeflowuniverse.herolib.ui.console +import freeflowuniverse.herolib.osal.core as osal @[heap] pub struct ScreensFactory { @@ -54,11 +55,12 @@ pub fn (mut self ScreensFactory) scan() ! { return } // there is stuff to parses - + // println(res.output) res1 := texttools.remove_empty_lines(res.output) .split_into_lines() .filter(it.starts_with(' ') || it.starts_with('\t')) .join_lines() + // println(res1) mut res2 := texttools.to_list_map('pre,state', res1, '').map(init_screen_object(it)) for mut item in res2 { if self.exists(item.name) { @@ -105,7 +107,6 @@ pub fn (mut self ScreensFactory) add(args_ ScreenAddArgs) !Screen { mut myscreen := self.get(args.name) or { return error('couldnt start screen with name ${args.name}, was not found afterwards.\ncmd:${args.cmd}\nScreens found.\n${self.str()}') } - if args.attach { myscreen.attach()! } @@ -137,15 +138,18 @@ pub fn (mut self ScreensFactory) start(name string) ! { return error("can't start screen with name:${name}, couldn't find.\nScreens found.\n${self.str()}") } s.start_()! + + osal.sleep(1) + for { self.scan()! + mut s2 := self.get(name) or { return error('couldnt start screen with name ${name}, was not found in screen scan.\ncmd:\n${s.cmd}\nScreens found.\n${self.str()}') } if s2.pid > 0 { return } - console.print_debug(s2.str()) time.sleep(100000) } } diff --git a/lib/osal/startupmanager/model.v b/lib/osal/startupmanager/model.v new file mode 100644 index 00000000..9f87d3c6 --- /dev/null +++ b/lib/osal/startupmanager/model.v @@ -0,0 +1,39 @@ +module startupmanager + +pub enum StartupManagerType { + unknown + screen + zinit + tmux + systemd + auto +} + +@[params] +pub struct ZProcessNewArgs { +pub mut: + name string @[required] + cmd string @[required] + cmd_stop string // command to stop (optional) + cmd_test string // command line to test service is running + workdir string // where to execute the commands + after []string // list of service we depend on + env map[string]string + oneshot bool + start bool = true + restart bool = true // whether the process should be restarted on failure + description string // not used in zinit + startuptype StartupManagerType +} + +fn startup_manager_type_get(c string) StartupManagerType { + match c { + 'unknown' { return .unknown } + 'screen' { return .screen } + 'zinit' { return .zinit } + 'tmux' { return .tmux } + 'systemd' { return .systemd } + 'auto' { return .auto } + else { return .unknown } + } +} diff --git a/lib/osal/startupmanager/readme.md b/lib/osal/startupmanager/readme.md index 29064ec5..3e94069e 100644 --- a/lib/osal/startupmanager/readme.md +++ b/lib/osal/startupmanager/readme.md @@ -1,16 +1,232 @@ -# startup manager +# Startup Manager -```go +The `startupmanager` module provides a unified interface for managing processes across different underlying startup systems like `screen`, `systemd`, and `zinit`. It abstracts away the complexities of each system, allowing you to start, stop, restart, delete, and query the status of processes using a consistent API. + +## How it Works + +The `StartupManager` struct acts as a facade, delegating calls to the appropriate underlying startup system based on the `StartupManagerType` configured or automatically detected. + +When you create a new `StartupManager` instance using `startupmanager.get()`, it attempts to detect if `zinit` is available on the system. If `zinit` is found, it will be used as the default startup manager. Otherwise, it falls back to `screen`. You can also explicitly specify the desired `StartupManagerType` during initialization. + +The `ZProcessNewArgs` struct defines the parameters for creating and managing a new process. + +## Usage + +### Initializing the Startup Manager + +You can initialize the `StartupManager` in a few ways: + +1. **Automatic Detection (Recommended):** + The manager will automatically detect if `zinit` is available and use it, otherwise it defaults to `screen`. + + ```v + import freeflowuniverse.herolib.osal.startupmanager + + fn main() { + mut sm := startupmanager.get(cat:.screen)! + // sm.cat will be .zinit or .screen + println("Using startup manager: ${sm.cat}") + } + ``` + +2. **Explicitly Specify Type:** + You can force the manager to use a specific type. + + ```v + import freeflowuniverse.herolib.osal.startupmanager + + fn main() { + mut sm_zinit := startupmanager.get(cat: .zinit)! + println("Using startup manager: ${sm_zinit.cat}") + + mut sm_screen := startupmanager.get(cat: .screen)! + println("Using startup manager: ${sm_screen.cat}") + + mut sm_systemd := startupmanager.get(cat: .systemd)! + println("Using startup manager: ${sm_systemd.cat}") + } + ``` + +### Managing Processes + +The following examples demonstrate how to use the `StartupManager` to interact with processes. The `new` method takes a `ZProcessNewArgs` struct to define the process. + +#### `new(args ZProcessNewArgs)`: Launch a new process + +This method creates and optionally starts a new process. + +```v import freeflowuniverse.herolib.osal.startupmanager -mut sm:=startupmanager.get()! +fn main() { + mut sm := startupmanager.get()! -sm.start( - name: 'myscreen' - cmd: 'htop' - description: '...' -)! + // Example: Starting a simple web server with zinit + sm.new( + name: "my_web_server" + cmd: "python3 -m http.server 8000" + start: true + restart: true + description: "A simple Python HTTP server" + startuptype: .zinit // Explicitly use zinit for this process + )! + println("Web server 'my_web_server' started with ${sm.cat}") + // Example: Starting a long-running script with screen + sm.new( + name: "my_background_script" + cmd: "bash -c 'while true; do echo Hello from script; sleep 5; done'" + start: true + restart: true + startuptype: .screen // Explicitly use screen for this process + )! + println("Background script 'my_background_script' started with ${sm.cat}") + + // Example: Starting a systemd service (requires root privileges and proper systemd setup) + // This assumes you have a systemd unit file configured for 'my_systemd_service' + // For example, a file like /etc/systemd/system/my_systemd_service.service + // [Unit] + // Description=My Systemd Service + // After=network.target + // + // [Service] + // ExecStart=/usr/bin/python3 -m http.server 8080 + // Restart=always + // + // [Install] + // WantedBy=multi-user.target + sm.new( + name: "my_systemd_service" + cmd: "python3 -m http.server 8080" // This command is used to generate the unit file if it doesn't exist + start: true + restart: true + startuptype: .systemd + )! + println("Systemd service 'my_systemd_service' created/started with ${sm.cat}") +} ``` +#### `start(name string)`: Start a process +Starts an existing process. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + sm.start("my_web_server")! + println("Process 'my_web_server' started.") +} +``` + +#### `stop(name string)`: Stop a process + +Stops a running process. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + sm.stop("my_web_server")! + println("Process 'my_web_server' stopped.") +} +``` + +#### `restart(name string)`: Restart a process + +Restarts a process. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + sm.restart("my_web_server")! + println("Process 'my_web_server' restarted.") +} +``` + +#### `delete(name string)`: Delete a process + +Removes a process from the startup manager. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + sm.delete("my_web_server")! + println("Process 'my_web_server' deleted.") +} +``` + +#### `status(name string) !ProcessStatus`: Get process status + +Returns the current status of a process. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + status := sm.status("my_web_server")! + println("Status of 'my_web_server': ${status}") +} +``` + +#### `running(name string) !bool`: Check if process is running + +Returns `true` if the process is active, `false` otherwise. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + is_running := sm.running("my_web_server")! + println("Is 'my_web_server' running? ${is_running}") +} +``` + +#### `output(name string) !string`: Get process output + +Retrieves the output (logs) of a process. Currently supported for `systemd`. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get(startupmanager.StartupManagerArgs{cat: .systemd})! + output := sm.output("my_systemd_service")! + println("Output of 'my_systemd_service':\n${output}") +} +``` + +#### `exists(name string) !bool`: Check if process exists + +Returns `true` if the process is known to the startup manager, `false` otherwise. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + does_exist := sm.exists("my_web_server")! + println("Does 'my_web_server' exist? ${does_exist}") +} +``` + +#### `list() ![]string`: List all managed services + +Returns a list of names of all services managed by the startup manager. + +```v +import freeflowuniverse.herolib.osal.startupmanager + +fn main() { + mut sm := startupmanager.get()! + services := sm.list()! + println("Managed services: ${services}") +} \ No newline at end of file diff --git a/lib/osal/startupmanager/startupmanager.v b/lib/osal/startupmanager/startupmanager.v index 2aaffae9..4e8c59e6 100644 --- a/lib/osal/startupmanager/startupmanager.v +++ b/lib/osal/startupmanager/startupmanager.v @@ -3,52 +3,33 @@ module startupmanager import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.screen import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit - -// // TODO: check if using this interface would simplify things -// pub interface StartupManagerI { -// new(args zinit.ZProcessNewArgs)! -// start(name string)! -// stop(name string)! -// restart(name string)! -// delete(name string)! -// status(name string) !ProcessStatus -// running(name string) !bool -// output(name string) !string -// exists(name string) !bool -// list_services() ![]string -// } - -pub enum StartupManagerType { - unknown - screen - zinit - tmux - systemd -} +// import freeflowuniverse.herolib.osal.zinit // Comment or remove this line +import freeflowuniverse.herolib.clients.zinit // Add this line pub struct StartupManager { pub mut: cat StartupManagerType } -@[params] -pub struct StartupManagerArgs { -pub mut: - cat StartupManagerType -} - -pub fn get(args StartupManagerArgs) !StartupManager { +pub fn get(cat StartupManagerType) !StartupManager { + console.print_debug('startupmanager get ${cat}') mut sm := StartupManager{ - cat: args.cat + cat: cat } - if args.cat == .unknown { - if zinit.check() { + if sm.cat == .auto { + // Try to get a ZinitRPC client and check if it can discover RPC methods. + // This implies the zinit daemon is running and accessible via its socket. + mut zinit_client_test := zinit.get(create: true)! // 'create:true' ensures a client object is initiated even if the socket isn't active. + if _ := zinit_client_test.rpc_discover() { sm.cat = .zinit } else { sm.cat = .screen } } + if sm.cat == .unknown { + print_backtrace() + return error("can't determine startup manager type, need to be a known one.") + } return sm } @@ -67,12 +48,9 @@ pub fn get(args StartupManagerArgs) !StartupManager { // restart bool = true // whether the process should be restarted on failure // description string //not used in zinit //``` -pub fn (mut sm StartupManager) new(args zinit.ZProcessNewArgs) ! { +pub fn (mut sm StartupManager) new(args ZProcessNewArgs) ! { console.print_debug("startupmanager start:${args.name} cmd:'${args.cmd}' restart:${args.restart}") mut mycat := sm.cat - if args.startuptype == .systemd { - mycat = .systemd - } match mycat { .screen { mut scr := screen.new(reset: false)! @@ -83,43 +61,57 @@ pub fn (mut sm StartupManager) new(args zinit.ZProcessNewArgs) ! { // console.print_debug('systemd start ${args.name}') mut systemdfactory := systemd.new()! systemdfactory.new( - cmd: args.cmd - name: args.name - description: args.description - start: args.start - restart: args.restart - env: args.env + cmd: args.cmd + name: args.name + start: args.start + restart: args.restart + env: args.env )! } .zinit { - console.print_debug('zinit start ${args.name}') - mut zinitfactory := zinit.new()! - // pub struct ZProcessNewArgs { - // name string @[required] - // cmd string @[required] - // cmd_stop string - // cmd_test string - // cmd_file bool // if we wanna force to run it as a file which is given to bash -c (not just a cmd in zinit) - // test string - // test_file bool - // after []string - // env map[string]string - // oneshot bool - // } - zinitfactory.new(args)! + console.print_debug('zinit start ${args.name} using clients.zinit') + // Get the Zinit RPC client instance. + // We assume it's properly configured (e.g., socket_path) via its factory setup. + mut zinit_client := zinit.get(create: true)! + + // Map ZProcessNewArgs to zinit.ServiceConfig + mut service_config := zinit.ServiceConfig{ + exec: args.cmd + test: args.cmd_test // Direct mapping + oneshot: args.oneshot // Use the oneshot flag directly + after: args.after // Direct mapping + log: "ring" + env: args.env // Direct mapping + dir: args.workdir // Direct mapping + shutdown_timeout: 0 // Default, or add to ZProcessNewArgs if needed + } + + // Create the service configuration file in zinit + zinit_client.service_create(args.name, service_config) or { + return error('Failed to create zinit service ${args.name}: ${err}') + } + + // If 'start' is true, also monitor and start the service + if args.start { + // Monitor loads the config, if it's new it starts it. + // If the service is already managed, this will bring it back up. + zinit_client.service_monitor(args.name) or { + return error('Failed to monitor zinit service ${args.name}: ${err}') + } + // Explicitly start the service (useful for oneshot services or if not already active) + zinit_client.service_start(args.name) or { + return error('Failed to start zinit service ${args.name}: ${err}') + } + } } else { panic('to implement, startup manager only support screen & systemd for now: ${mycat}') } } - // if args.start { - // sm.start(args.name)! - // } else if args.restart { - // sm.restart(args.name)! - // } } pub fn (mut sm StartupManager) start(name string) ! { + $dbg; match sm.cat { .screen { return @@ -136,9 +128,11 @@ pub fn (mut sm StartupManager) start(name string) ! { } } .zinit { - console.print_debug('zinit process start ${name}') - mut zinitfactory := zinit.new()! - zinitfactory.start(name)! + console.print_debug('zinit process start ${name} using clients.zinit') + mut zinit_client := zinit.get()! // Get the already configured zinit client + zinit_client.service_start(name) or { + return error('Failed to start zinit service ${name}: ${err}') + } } else { panic('to implement, startup manager only support screen for now') @@ -163,11 +157,10 @@ pub fn (mut sm StartupManager) stop(name string) ! { } } .zinit { - console.print_debug('zinit stop ${name}') - mut zinitfactory := zinit.new()! - zinitfactory.load()! - if zinitfactory.exists(name) { - zinitfactory.stop(name)! + console.print_debug('zinit stop ${name} using clients.zinit') + mut zinit_client := zinit.get()! // Get the already configured zinit client + zinit_client.service_stop(name) or { + return error('Failed to stop zinit service ${name}: ${err}') } } else { @@ -189,10 +182,15 @@ pub fn (mut sm StartupManager) restart(name string) ! { systemdprocess.restart()! } .zinit { - console.print_debug('zinit restart ${name}') - mut zinitfactory := zinit.new()! - zinitfactory.stop(name)! - zinitfactory.start(name)! + console.print_debug('zinit restart ${name} using clients.zinit') + mut zinit_client := zinit.get()! // Get the already configured zinit client + // Zinit's 'start' method can act as a restart if the service is already running. + // For a clean restart, you might explicitly stop and then start, but service_start + // in Zinit is generally idempotent and will manage the state. + zinit_client.service_stop(name) or {} + zinit_client.service_start(name) or { + return error('Failed to restart zinit service ${name}: ${err}') + } } else { panic('to implement, startup manager only support screen for now') @@ -215,10 +213,13 @@ pub fn (mut sm StartupManager) delete(name string) ! { systemdprocess.delete()! } .zinit { - mut zinitfactory := zinit.new()! - zinitfactory.load()! - if zinitfactory.exists(name) { - zinitfactory.delete(name)! + console.print_debug('zinit delete ${name} using clients.zinit') + mut zinit_client := zinit.get()! // Get the already configured zinit client + // To properly delete, first stop monitoring and then stop the service, before deleting the configuration. + zinit_client.service_forget(name) or {} + zinit_client.service_stop(name) or {} + zinit_client.service_delete(name) or { + return error('Failed to delete zinit service ${name}: ${err}') } } else { @@ -260,23 +261,41 @@ pub fn (mut sm StartupManager) status(name string) !ProcessStatus { return s } .zinit { - mut zinitfactory := zinit.new()! - mut p := zinitfactory.get(name) or { return .unknown } - // unknown - // init - // ok - // killed - // error - // blocked - // spawned - match mut p.status()! { - .init { return .activating } - .ok { return .active } - .error { return .failed } - .blocked { return .inactive } - .killed { return .inactive } - .spawned { return .activating } - .unknown { return .unknown } + console.print_debug('zinit status ${name} using clients.zinit') + mut zinit_client := zinit.get()! + // Attempt to get the service status. Handle "Service not found" as .unknown. + status_info := zinit_client.service_status(name) or { + err_val := err.msg().to_lower() + if err_val.contains('service not found') { + return .unknown + } else { + return error('Failed to get zinit service status: ${err}') + } + } + + // Map Zinit's ServiceStatus.state to StartupManager's ProcessStatus + match status_info.state.to_lower() { + 'running', 'success' { + return .active + } // Zinit considers 'success' for one-shot tasks as complete & successful + 'error', 'broken' { + return .failed + } + 'starting' { + return .activating + } + 'stopping' { + return .deactivating + } + // Zinit has other states like 'paused', 'restarting', 'waiting', etc. + // We'll map them to closest equivalents or .unknown for now. + 'stopped', 'restarted', 'forgotten' { + return .inactive + } // 'restarted' here means it's about to be 'running' again, but in the context of a single status check it might be transient. For simplicity map it to inactive here. + else { + console.print_debug('Unknown Zinit state for ${name}: ${status_info.state}') + return .unknown + } } } else { @@ -302,6 +321,15 @@ pub fn (mut sm StartupManager) output(name string) !string { .systemd { return systemd.journalctl(service: name)! } + .zinit { + console.print_debug('zinit output ${name} using clients.zinit') + mut zinit_client := zinit.get()! + // Calls stream_current_logs with a name filter. + logs := zinit_client.stream_current_logs(zinit.LogParams{ name: name }) or { + return error('Failed to get zinit logs for ${name}: ${err}') + } + return logs.join('\n') + } else { panic('to implement, startup manager only support screen & systemd for now ${sm.cat}') } @@ -309,9 +337,10 @@ pub fn (mut sm StartupManager) output(name string) !string { } pub fn (mut sm StartupManager) exists(name string) !bool { - println(sm.cat) if sm.cat == .unknown { - if zinit.check() { + // If type is auto/unknown, try to determine. + mut zinit_client_test := zinit.get(create: true)! + if _ := zinit_client_test.rpc_discover() { sm.cat = .zinit } else { sm.cat = .screen @@ -328,10 +357,10 @@ pub fn (mut sm StartupManager) exists(name string) !bool { return systemdfactory.exists(name) } .zinit { - // console.print_debug("exists sm zinit check ${name}") - mut zinitfactory := zinit.new()! - zinitfactory.load()! - return zinitfactory.exists(name) + console.print_debug('zinit exists ${name} using clients.zinit') + mut zinit_client := zinit.get()! + zinit_client.service_status(name) or { return false } + return true } else { panic('to implement. startup manager only support screen & systemd for now ${sm.cat}') @@ -351,38 +380,20 @@ pub fn (mut sm StartupManager) list() ![]string { return systemdfactory.names() } .zinit { - mut zinitfactory := zinit.new()! - return zinitfactory.names() + console.print_debug('zinit list using clients.zinit') + mut zinit_client := zinit.get()! + // service_list returns a map[string]string (name -> state). We only need the names. + service_map := zinit_client.service_list() or { + return error('Failed to list zinit services: ${err}') + } + mut names := []string{} + for name in service_map.keys() { + names << name + } + return names } else { panic('to implement. startup manager only support screen & systemd for now: ${sm.cat}') } } } - -// THIS IS PROBABLY PART OF OTHER MODULE NOW - -// pub struct SecretArgs { -// pub mut: -// name string @[required] -// cat SecretType -// } - -// pub enum SecretType { -// normal -// } - -// // creates a secret if it doesn exist yet -// pub fn (mut sm StartupManager) secret(args SecretArgs) !string { -// if !(sm.exists(args.name)) { -// return error("can't find screen with name ${args.name}, for secret") -// } -// key := 'secrets:startup:${args.name}' -// mut redis := redisclient.core_get()! -// mut secret := redis.get(key)! -// if secret.len == 0 { -// secret = rand.hex(16) -// redis.set(key, secret)! -// } -// return secret -// } diff --git a/lib/osal/startupmanager/startupmanager_test.v b/lib/osal/startupmanager/startupmanager_test.v index f8f8029f..ce317ed8 100644 --- a/lib/osal/startupmanager/startupmanager_test.v +++ b/lib/osal/startupmanager/startupmanager_test.v @@ -1,8 +1,8 @@ module startupmanager -import freeflowuniverse.herolib.ui.console +// import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.osal.screen -import freeflowuniverse.herolib.osal.systemd +// import freeflowuniverse.herolib.osal.systemd import os import time @@ -24,7 +24,7 @@ pub fn testsuite_begin() ! { } // Clean up any existing process - mut sm := get()! + mut sm := get(.auto) if sm.exists(process_name)! { sm.stop(process_name)! time.sleep(200 * time.millisecond) // Give time for cleanup @@ -32,7 +32,7 @@ pub fn testsuite_begin() ! { } pub fn testsuite_end() ! { - mut sm := get()! + mut sm := get(.auto) if sm.exists(process_name)! { sm.stop(process_name)! time.sleep(200 * time.millisecond) // Give time for cleanup @@ -49,7 +49,7 @@ pub fn testsuite_end() ! { // Test startup manager status functionality pub fn test_status() ! { - mut sm := get()! + mut sm := get(.auto) mut screen_factory := screen.new(reset: false)! // Create and ensure process doesn't exist @@ -85,7 +85,7 @@ pub fn test_status() ! { // Test process creation with description pub fn test_process_with_description() ! { - mut sm := get()! + mut sm := get(.auto) mut screen_factory := screen.new(reset: false)! description := 'Test process with custom description' @@ -123,7 +123,7 @@ pub fn test_process_with_description() ! { // Test error handling pub fn test_error_handling() ! { - mut sm := get()! + mut sm := get(.auto) mut screen_factory := screen.new(reset: false)! // Test non-existent process diff --git a/lib/osal/zinit/implementation_plan.md b/lib/osal/zinit/implementation_plan.md deleted file mode 100644 index c540dac6..00000000 --- a/lib/osal/zinit/implementation_plan.md +++ /dev/null @@ -1,224 +0,0 @@ -# Implementation Plan: Zinit OpenRPC Client Refactoring - -## Current State Analysis -- Multiple implementations (zinit.v, zinit_stateless.v, rpc.v, zprocess.v) -- Inconsistent use of OpenRPC vs. direct filesystem operations -- Duplication of functionality across multiple files -- Lack of a unified approach - -## Implementation Plan - -### Phase 1: Create a New Unified OpenRPC Client -1. Create a new file `zinit_client.v` with a unified `ZinitClient` struct -2. Implement all methods using the OpenRPC protocol exclusively -3. Ensure the client handles all error cases properly -4. Add comprehensive documentation for all methods - -```mermaid -graph TD - A[Create New OpenRPC Client] --> B[Implement Core Client Methods] - B --> C[Implement Service Management Methods] - C --> D[Implement Configuration Management Methods] - D --> E[Implement System Operations Methods] - E --> F[Implement Logging Methods] -``` - -### Phase 2: Refactor Existing Implementations -1. Refactor `ZinitStateless` to use the new client for all operations -2. Refactor `Zinit` to use the new client for all operations -3. Refactor `ZProcess` to use the new client for all operations -4. Update factory methods to use the new client -5. Ensure backward compatibility by maintaining the same API/interface - -```mermaid -graph TD - A[Refactor ZinitStateless] --> B[Refactor Zinit] - B --> C[Refactor ZProcess] - C --> D[Update Factory Methods] - D --> E[Ensure Backward Compatibility] -``` - -### Phase 3: Update Tests and Examples -1. Update existing tests to use the new client -2. Add new tests to cover all OpenRPC methods -3. Update examples to demonstrate the new client -4. Create new examples to showcase best practices - -```mermaid -graph TD - A[Update Unit Tests] --> B[Update Integration Tests] - B --> C[Update Examples] - C --> D[Create New Examples] -``` - -### Phase 4: Documentation and Cleanup -1. Update the README with comprehensive documentation -2. Add detailed API documentation for all methods -3. Add usage examples for common scenarios -4. Mark old implementations as deprecated (with a migration guide) - -```mermaid -graph TD - A[Update README] --> B[Add API Documentation] - B --> C[Add Usage Examples] - C --> D[Mark Deprecated Code] -``` - -## Detailed Implementation Steps - -### 1. Create New OpenRPC Client (zinit_client.v) - -```v -module zinit - -import freeflowuniverse.herolib.schemas.jsonrpc -import json - -// ZinitClient is a unified client for interacting with Zinit using OpenRPC -pub struct ZinitClient { -pub mut: - rpc_client &jsonrpc.Client -} - -// new_client creates a new Zinit OpenRPC client -pub fn new_client(socket_path string) ZinitClient { - mut cl := jsonrpc.new_unix_socket_client(socket_path) - return ZinitClient{ - rpc_client: cl - } -} - -// Implement all OpenRPC methods... -``` - -### 2. Refactor ZinitStateless (zinit_stateless.v) - -```v -module zinit - -import freeflowuniverse.herolib.core.pathlib - -@[params] -pub struct ZinitConfig { - path string = '/etc/zinit' - pathcmds string = '/etc/zinit/cmds' - socket_path string = default_socket_path -} - -pub struct ZinitStateless { -pub mut: - client ZinitClient - path pathlib.Path - pathcmds pathlib.Path -} - -pub fn new_stateless(z ZinitConfig) !ZinitStateless { - return ZinitStateless{ - client: new_client(z.socket_path) - path: pathlib.get_dir(path: z.path, create: true)! - pathcmds: pathlib.get_dir(path: z.pathcmds, create: true)! - } -} - -// Refactor methods to use the OpenRPC client... -``` - -### 3. Refactor Zinit (zinit.v) - -```v -module zinit - -import freeflowuniverse.herolib.core.pathlib - -@[heap] -pub struct Zinit { -pub mut: - processes map[string]ZProcess - path pathlib.Path - pathcmds pathlib.Path - client ZinitClient -} - -// Refactor methods to use the OpenRPC client... -``` - -### 4. Refactor ZProcess (zprocess.v) - -```v -module zinit - -pub struct ZProcess { -pub: - name string = 'default' -pub mut: - cmd string - cmd_stop string - cmd_test string - workdir string - status ZProcessStatus - pid int - after []string - env map[string]string - oneshot bool - start bool = true - restart bool = true - description string - client &ZinitClient -} - -// Refactor methods to use the OpenRPC client... -``` - -## Key Changes Required - -1. **Replace Direct Filesystem Operations**: - - Replace file creation/modification with `service_create` OpenRPC calls - - Replace file deletion with `service_delete` OpenRPC calls - - Replace file reading with `service_get` OpenRPC calls - -2. **Replace Shell Commands**: - - Replace `zinit list` shell commands with `service_list` OpenRPC calls - - Replace `zinit status` shell commands with `service_status` OpenRPC calls - - Replace `zinit log` shell commands with `stream_currentLogs` OpenRPC calls - -3. **Unify Error Handling**: - - Implement consistent error handling across all methods - - Properly propagate OpenRPC error responses to the caller - -4. **Maintain Backward Compatibility**: - - Keep the same method signatures for public methods - - Ensure the same behavior for all methods - - Add deprecation notices for methods that will be removed in the future - -## Testing Strategy - -1. **Unit Tests**: - - Test each OpenRPC method individually - - Test error handling for each method - - Test with mock responses for predictable testing - -2. **Integration Tests**: - - Test with a real Zinit instance - - Test the full lifecycle of services (create, start, status, stop, delete) - - Test edge cases and error conditions - -3. **Backward Compatibility Tests**: - - Test existing code that uses the old implementations - - Ensure no regressions in functionality - -## Documentation Updates - -1. **README.md**: - - Update with comprehensive documentation - - Add examples for common use cases - - Add migration guide for users of the old implementations - -2. **API Documentation**: - - Document all public methods - - Document all structs and their fields - - Document error conditions and how to handle them - -3. **Examples**: - - Update existing examples - - Add new examples for common use cases - - Add examples for error handling \ No newline at end of file diff --git a/lib/osal/zinit/openrpc.json b/lib/osal/zinit/openrpc.json deleted file mode 100644 index 6e3e3eee..00000000 --- a/lib/osal/zinit/openrpc.json +++ /dev/null @@ -1,873 +0,0 @@ -{ - "openrpc": "1.2.6", - "info": { - "version": "1.0.0", - "title": "Zinit JSON-RPC API", - "description": "JSON-RPC 2.0 API for controlling and querying Zinit services", - "license": { - "name": "MIT" - } - }, - "servers": [ - { - "name": "Unix Socket", - "url": "unix:///tmp/zinit.sock" - } - ], - "methods": [ - { - "name": "rpc.discover", - "description": "Returns the OpenRPC specification for the API", - "params": [], - "result": { - "name": "OpenRPCSpec", - "description": "The OpenRPC specification", - "schema": { - "type": "object" - } - }, - "examples": [ - { - "name": "Get API specification", - "params": [], - "result": { - "name": "OpenRPCSpecResult", - "value": { - "openrpc": "1.2.6", - "info": { - "version": "1.0.0", - "title": "Zinit JSON-RPC API" - } - } - } - } - ] - }, - { - "name": "service_list", - "description": "Lists all services managed by Zinit", - "params": [], - "result": { - "name": "ServiceList", - "description": "A map of service names to their current states", - "schema": { - "type": "object", - "additionalProperties": { - "type": "string", - "description": "Service state (Running, Success, Error, etc.)" - } - } - }, - "examples": [ - { - "name": "List all services", - "params": [], - "result": { - "name": "ServiceListResult", - "value": { - "service1": "Running", - "service2": "Success", - "service3": "Error" - } - } - } - ] - }, - { - "name": "service_status", - "description": "Shows detailed status information for a specific service", - "params": [ - { - "name": "name", - "description": "The name of the service", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "ServiceStatus", - "description": "Detailed status information for the service", - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Service name" - }, - "pid": { - "type": "integer", - "description": "Process ID of the running service (if running)" - }, - "state": { - "type": "string", - "description": "Current state of the service (Running, Success, Error, etc.)" - }, - "target": { - "type": "string", - "description": "Target state of the service (Up, Down)" - }, - "after": { - "type": "object", - "description": "Dependencies of the service and their states", - "additionalProperties": { - "type": "string", - "description": "State of the dependency" - } - } - } - } - }, - "examples": [ - { - "name": "Get status of redis service", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "ServiceStatusResult", - "value": { - "name": "redis", - "pid": 1234, - "state": "Running", - "target": "Up", - "after": { - "dependency1": "Success", - "dependency2": "Running" - } - } - } - } - ], - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "service name \"unknown\" unknown" - } - ] - }, - { - "name": "service_start", - "description": "Starts a service", - "params": [ - { - "name": "name", - "description": "The name of the service to start", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "StartResult", - "description": "Result of the start operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Start redis service", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "StartResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "service name \"unknown\" unknown" - } - ] - }, - { - "name": "service_stop", - "description": "Stops a service", - "params": [ - { - "name": "name", - "description": "The name of the service to stop", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "StopResult", - "description": "Result of the stop operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Stop redis service", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "StopResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "service name \"unknown\" unknown" - }, - { - "code": -32003, - "message": "Service is down", - "data": "service \"redis\" is down" - } - ] - }, - { - "name": "service_monitor", - "description": "Starts monitoring a service. The service configuration is loaded from the config directory.", - "params": [ - { - "name": "name", - "description": "The name of the service to monitor", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "MonitorResult", - "description": "Result of the monitor operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Monitor redis service", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "MonitorResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32001, - "message": "Service already monitored", - "data": "service \"redis\" already monitored" - }, - { - "code": -32005, - "message": "Config error", - "data": "failed to load service configuration" - } - ] - }, - { - "name": "service_forget", - "description": "Stops monitoring a service. You can only forget a stopped service.", - "params": [ - { - "name": "name", - "description": "The name of the service to forget", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "ForgetResult", - "description": "Result of the forget operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Forget redis service", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "ForgetResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "service name \"unknown\" unknown" - }, - { - "code": -32002, - "message": "Service is up", - "data": "service \"redis\" is up" - } - ] - }, - { - "name": "service_kill", - "description": "Sends a signal to a running service", - "params": [ - { - "name": "name", - "description": "The name of the service to send the signal to", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "signal", - "description": "The signal to send (e.g., SIGTERM, SIGKILL)", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "KillResult", - "description": "Result of the kill operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Send SIGTERM to redis service", - "params": [ - { - "name": "name", - "value": "redis" - }, - { - "name": "signal", - "value": "SIGTERM" - } - ], - "result": { - "name": "KillResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "service name \"unknown\" unknown" - }, - { - "code": -32003, - "message": "Service is down", - "data": "service \"redis\" is down" - }, - { - "code": -32004, - "message": "Invalid signal", - "data": "invalid signal: INVALID" - } - ] - }, - { - "name": "system_shutdown", - "description": "Stops all services and powers off the system", - "params": [], - "result": { - "name": "ShutdownResult", - "description": "Result of the shutdown operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Shutdown the system", - "params": [], - "result": { - "name": "ShutdownResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32006, - "message": "Shutting down", - "data": "system is already shutting down" - } - ] - }, - { - "name": "system_reboot", - "description": "Stops all services and reboots the system", - "params": [], - "result": { - "name": "RebootResult", - "description": "Result of the reboot operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Reboot the system", - "params": [], - "result": { - "name": "RebootResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32006, - "message": "Shutting down", - "data": "system is already shutting down" - } - ] - }, - { - "name": "service_create", - "description": "Creates a new service configuration file", - "params": [ - { - "name": "name", - "description": "The name of the service to create", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "content", - "description": "The service configuration content", - "required": true, - "schema": { - "type": "object", - "properties": { - "exec": { - "type": "string", - "description": "Command to run" - }, - "oneshot": { - "type": "boolean", - "description": "Whether the service should be restarted" - }, - "after": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Services that must be running before this one starts" - }, - "log": { - "type": "string", - "enum": ["null", "ring", "stdout"], - "description": "How to handle service output" - }, - "env": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "description": "Environment variables for the service" - }, - "shutdown_timeout": { - "type": "integer", - "description": "Maximum time to wait for service to stop during shutdown" - } - } - } - } - ], - "result": { - "name": "CreateServiceResult", - "description": "Result of the create operation", - "schema": { - "type": "string" - } - }, - "errors": [ - { - "code": -32007, - "message": "Service already exists", - "data": "Service 'name' already exists" - }, - { - "code": -32008, - "message": "Service file error", - "data": "Failed to create service file" - } - ] - }, - { - "name": "service_delete", - "description": "Deletes a service configuration file", - "params": [ - { - "name": "name", - "description": "The name of the service to delete", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "DeleteServiceResult", - "description": "Result of the delete operation", - "schema": { - "type": "string" - } - }, - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "Service 'name' not found" - }, - { - "code": -32008, - "message": "Service file error", - "data": "Failed to delete service file" - } - ] - }, - { - "name": "service_get", - "description": "Gets a service configuration file", - "params": [ - { - "name": "name", - "description": "The name of the service to get", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "GetServiceResult", - "description": "The service configuration", - "schema": { - "type": "object" - } - }, - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "Service 'name' not found" - }, - { - "code": -32008, - "message": "Service file error", - "data": "Failed to read service file" - } - ] - }, - { - "name": "service_stats", - "description": "Get memory and CPU usage statistics for a service", - "params": [ - { - "name": "name", - "description": "The name of the service to get stats for", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "ServiceStats", - "description": "Memory and CPU usage statistics for the service", - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Service name" - }, - "pid": { - "type": "integer", - "description": "Process ID of the service" - }, - "memory_usage": { - "type": "integer", - "description": "Memory usage in bytes" - }, - "cpu_usage": { - "type": "number", - "description": "CPU usage as a percentage (0-100)" - }, - "children": { - "type": "array", - "description": "Stats for child processes", - "items": { - "type": "object", - "properties": { - "pid": { - "type": "integer", - "description": "Process ID of the child process" - }, - "memory_usage": { - "type": "integer", - "description": "Memory usage in bytes" - }, - "cpu_usage": { - "type": "number", - "description": "CPU usage as a percentage (0-100)" - } - } - } - } - } - } - }, - "examples": [ - { - "name": "Get stats for redis service", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "ServiceStatsResult", - "value": { - "name": "redis", - "pid": 1234, - "memory_usage": 10485760, - "cpu_usage": 2.5, - "children": [ - { - "pid": 1235, - "memory_usage": 5242880, - "cpu_usage": 1.2 - } - ] - } - } - } - ], - "errors": [ - { - "code": -32000, - "message": "Service not found", - "data": "service name \"unknown\" unknown" - }, - { - "code": -32003, - "message": "Service is down", - "data": "service \"redis\" is down" - } - ] - }, - { - "name": "system_start_http_server", - "description": "Start an HTTP/RPC server at the specified address", - "params": [ - { - "name": "address", - "description": "The network address to bind the server to (e.g., '127.0.0.1:8080')", - "required": true, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "StartHttpServerResult", - "description": "Result of the start HTTP server operation", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Start HTTP server on localhost:8080", - "params": [ - { - "name": "address", - "value": "127.0.0.1:8080" - } - ], - "result": { - "name": "StartHttpServerResult", - "value": "HTTP server started at 127.0.0.1:8080" - } - } - ], - "errors": [ - { - "code": -32602, - "message": "Invalid address", - "data": "Invalid network address format" - } - ] - }, - { - "name": "system_stop_http_server", - "description": "Stop the HTTP/RPC server if running", - "params": [], - "result": { - "name": "StopHttpServerResult", - "description": "Result of the stop HTTP server operation", - "schema": { - "type": "null" - } - }, - "examples": [ - { - "name": "Stop the HTTP server", - "params": [], - "result": { - "name": "StopHttpServerResult", - "value": null - } - } - ], - "errors": [ - { - "code": -32602, - "message": "Server not running", - "data": "No HTTP server is currently running" - } - ] - }, - { - "name": "stream_currentLogs", - "description": "Get current logs from zinit and monitored services", - "params": [ - { - "name": "name", - "description": "Optional service name filter. If provided, only logs from this service will be returned", - "required": false, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "LogsResult", - "description": "Array of log strings", - "schema": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "examples": [ - { - "name": "Get all logs", - "params": [], - "result": { - "name": "LogsResult", - "value": [ - "2023-01-01T12:00:00 redis: Starting service", - "2023-01-01T12:00:01 nginx: Starting service" - ] - } - }, - { - "name": "Get logs for a specific service", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "LogsResult", - "value": [ - "2023-01-01T12:00:00 redis: Starting service", - "2023-01-01T12:00:02 redis: Service started" - ] - } - } - ] - }, - { - "name": "stream_subscribeLogs", - "description": "Subscribe to log messages generated by zinit and monitored services", - "params": [ - { - "name": "name", - "description": "Optional service name filter. If provided, only logs from this service will be returned", - "required": false, - "schema": { - "type": "string" - } - } - ], - "result": { - "name": "LogSubscription", - "description": "A subscription to log messages", - "schema": { - "type": "string" - } - }, - "examples": [ - { - "name": "Subscribe to all logs", - "params": [], - "result": { - "name": "LogSubscription", - "value": "2023-01-01T12:00:00 redis: Service started" - } - }, - { - "name": "Subscribe to filtered logs", - "params": [ - { - "name": "name", - "value": "redis" - } - ], - "result": { - "name": "LogSubscription", - "value": "2023-01-01T12:00:00 redis: Service started" - } - } - ] - } - ] -} \ No newline at end of file diff --git a/lib/schemas/jsonrpcmodel/model_openrpc_spec.v b/lib/schemas/jsonrpcmodel/model_openrpc_spec.v new file mode 100644 index 00000000..66f27d01 --- /dev/null +++ b/lib/schemas/jsonrpcmodel/model_openrpc_spec.v @@ -0,0 +1,40 @@ +module jsonrpcmodel + +// OpenRPCSpec represents the OpenRPC specification structure +pub struct OpenRPCSpec { +pub mut: + openrpc string @[json: 'openrpc'] // OpenRPC version + info OpenRPCInfo @[json: 'info'] // API information + methods []OpenRPCMethod @[json: 'methods'] // Available methods + servers []OpenRPCServer @[json: 'servers'] // Server information +} + +// OpenRPCInfo represents API information +pub struct OpenRPCInfo { +pub mut: + version string @[json: 'version'] // API version + title string @[json: 'title'] // API title + description string @[json: 'description'] // API description + license OpenRPCLicense @[json: 'license'] // License information +} + +// OpenRPCLicense represents license information +pub struct OpenRPCLicense { +pub mut: + name string @[json: 'name'] // License name +} + +// OpenRPCMethod represents an RPC method +pub struct OpenRPCMethod { +pub mut: + name string @[json: 'name'] // Method name + description string @[json: 'description'] // Method description + // Note: params and result are dynamic and would need more complex handling +} + +// OpenRPCServer represents server information +pub struct OpenRPCServer { +pub mut: + name string @[json: 'name'] // Server name + url string @[json: 'url'] // Server URL +} diff --git a/lib/virt/docker/docker_recipe_code.v b/lib/virt/docker/docker_recipe_code.v index 2601185c..0cb5d916 100644 --- a/lib/virt/docker/docker_recipe_code.v +++ b/lib/virt/docker/docker_recipe_code.v @@ -18,7 +18,7 @@ pub mut: // checkout a code repository on right location pub fn (mut r DockerBuilderRecipe) add_codeget(args_ CodeGetArgs) ! { mut args := args_ - mut gs := gittools.get(coderoot: '${r.path()}/code')! + mut gs := gittools.new(coderoot: '${r.path()}/code')! mut gr := gs.get_repo(url: args.url, pull: args.pull, reset: args.reset)! if args.name == '' { diff --git a/lib/web/docusaurus/config.v b/lib/web/docusaurus/config.v index f2963db8..e09fae1f 100644 --- a/lib/web/docusaurus/config.v +++ b/lib/web/docusaurus/config.v @@ -4,9 +4,9 @@ import os import freeflowuniverse.herolib.core.pathlib __global ( - docusaurus_sites map[string]&DocSite + docusaurus_sites map[string]&DocSite docusaurus_config []DocusaurusConfigParams - docusaurus_last string //the last one we worked with + docusaurus_last string // the last one we worked with ) pub struct DocusaurusConfig { @@ -16,7 +16,7 @@ pub mut: install bool reset bool template_update bool - coderoot string + coderoot string } @[params] @@ -27,16 +27,16 @@ pub mut: install bool reset bool template_update bool - coderoot string + coderoot string } -//return the last know config +// return the last know config pub fn config() !DocusaurusConfig { if docusaurus_config.len == 0 { docusaurus_config << DocusaurusConfigParams{} } - mut args:= docusaurus_config[0] or { panic("bug in docusaurus config") } - if args.path_build == '' { + mut args := docusaurus_config[0] or { panic('bug in docusaurus config') } + if args.path_build == '' { args.path_build = '${os.home_dir()}/hero/var/docusaurus/build' } if args.path_publish == '' { @@ -47,16 +47,16 @@ pub fn config() !DocusaurusConfig { } mut c := DocusaurusConfig{ - path_publish: pathlib.get_dir(path: args.path_publish, create: true)! - path_build: pathlib.get_dir(path: args.path_build, create: true)! - coderoot: args.coderoot - install: args.install - reset: args.reset + path_publish: pathlib.get_dir(path: args.path_publish, create: true)! + path_build: pathlib.get_dir(path: args.path_build, create: true)! + coderoot: args.coderoot + install: args.install + reset: args.reset template_update: args.template_update } if c.install { install(c)! - c.install=true + c.install = true } return c } diff --git a/lib/web/docusaurus/dsite.v b/lib/web/docusaurus/dsite.v index 81868ab3..d992ca37 100644 --- a/lib/web/docusaurus/dsite.v +++ b/lib/web/docusaurus/dsite.v @@ -8,8 +8,8 @@ import freeflowuniverse.herolib.ui.console @[heap] pub struct DocSite { pub mut: - name string - url string + name string + url string // path_src pathlib.Path path_publish pathlib.Path path_build pathlib.Path @@ -17,7 +17,7 @@ pub mut: config Configuration website sitemodule.Site generated bool - } +} pub fn (mut s DocSite) build() ! { s.generate()! @@ -51,20 +51,19 @@ pub fn (mut s DocSite) build_publish() ! { retry: 0 )! for item in s.website.siteconfig.build_dest { - if item.path.trim_space().trim("/ ") == "" { - $if debug{ + if item.path.trim_space().trim('/ ') == '' { + $if debug { print_backtrace() } - return error("build destination path is empty for docusaurus.") + return error('build destination path is empty for docusaurus.') } osal.exec( - cmd: ' + cmd: ' cd ${s.path_build.path} rsync -avz --delete -e "ssh -p 22 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" build/ ${item.path} ' )! } - } @[params] diff --git a/lib/web/docusaurus/dsite_generate.v b/lib/web/docusaurus/dsite_generate.v index e55b7da9..26a8cf7a 100644 --- a/lib/web/docusaurus/dsite_generate.v +++ b/lib/web/docusaurus/dsite_generate.v @@ -1,6 +1,5 @@ module docusaurus - import freeflowuniverse.herolib.core.pathlib import json import os @@ -16,8 +15,8 @@ pub fn (mut docsite DocSite) generate() ! { console.print_header(' docsite generate: ${docsite.name} on ${c.path_build.path}') osal.rm('${c.path_build.path}/docs')! - - cfg_path:="${c.path_build.path}/cfg" + + cfg_path := '${c.path_build.path}/cfg' osal.rm(cfg_path)! mut main_file := pathlib.get_file(path: '${cfg_path}/main.json', create: true)! @@ -32,5 +31,4 @@ pub fn (mut docsite DocSite) generate() ! { docsite.generate_docs()! docsite.import()! - } diff --git a/lib/web/docusaurus/dsite_generate_docs.v b/lib/web/docusaurus/dsite_generate_docs.v index b42a13d8..d6d71216 100644 --- a/lib/web/docusaurus/dsite_generate_docs.v +++ b/lib/web/docusaurus/dsite_generate_docs.v @@ -20,10 +20,9 @@ mut: // Generate docs from site configuration pub fn (mut docsite DocSite) generate_docs() ! { - c := config()! - //we generate the docs in the build path + // we generate the docs in the build path docs_path := '${c.path_build.path}/docs' mut gen := SiteGenerator{ diff --git a/lib/web/docusaurus/dsite_import.v b/lib/web/docusaurus/dsite_import.v index 08ab4065..38285dae 100644 --- a/lib/web/docusaurus/dsite_import.v +++ b/lib/web/docusaurus/dsite_import.v @@ -6,13 +6,10 @@ import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools.regext - - pub fn (mut docsite DocSite) import() ! { for importparams in docsite.website.siteconfig.imports { - console.print_header('Importing: path:${importparams.path} or url:${importparams.url}') - + // pub struct ImportItem { // name string // will normally be empty // url string // http git url can be to specific path @@ -22,14 +19,14 @@ pub fn (mut docsite DocSite) import() ! { // visible bool = true // } - c:=config()! + c := config()! - if importparams.path == "" && importparams.url != "" { - return error("in import for docusaurus need to specify url or path") + if importparams.path == '' && importparams.url != '' { + return error('in import for docusaurus need to specify url or path') } // Use gittools to get path of what we want to import - import_path := gittools.get_repo_path( + mut import_path := gittools.path( git_pull: c.reset git_reset: c.reset git_url: importparams.url @@ -37,13 +34,14 @@ pub fn (mut docsite DocSite) import() ! { path: importparams.path )! - mut import_patho := pathlib.get(import_path) - - if importparams.dest.starts_with("/") { - return error("Import path ${importparams.dest} must be relative, will be relative in relation to the build dir.") + if import_path.path == '' { + return error('import path not found for url:${importparams.url} and path:${importparams.path}') + } + if importparams.dest.starts_with('/') { + return error('Import path ${importparams.dest} must be relative, will be relative in relation to the build dir.') } - import_patho.copy(dest: '${c.path_build.path}/${importparams.dest}', delete: false)! + import_path.copy(dest: '${c.path_build.path}/${importparams.dest}', delete: false)! // println(importparams) // replace: {'NAME': 'MyName', 'URGENCY': 'red'} diff --git a/lib/web/docusaurus/factory.v b/lib/web/docusaurus/factory.v index 33c98104..7a727c03 100644 --- a/lib/web/docusaurus/factory.v +++ b/lib/web/docusaurus/factory.v @@ -24,7 +24,7 @@ pub fn dsite_define(sitename string) ! { // Create the DocSite instance mut dsite := &DocSite{ name: sitename - path_publish: pathlib.get_dir(path: "${path_build_}/build", create: true)! + path_publish: pathlib.get_dir(path: '${path_build_}/build', create: true)! path_build: pathlib.get_dir(path: path_build_, create: true)! config: new_configuration(website.siteconfig)! website: website @@ -36,7 +36,7 @@ pub fn dsite_define(sitename string) ! { pub fn dsite_get(name_ string) !&DocSite { mut name := texttools.name_fix(name_) - if name=="" { + if name == '' { name = docusaurus_last } return docusaurus_sites[name] or { @@ -46,9 +46,9 @@ pub fn dsite_get(name_ string) !&DocSite { pub fn dsite_exists(name_ string) !bool { mut name := texttools.name_fix(name_) - if name=="" { + if name == '' { name = docusaurus_last - } + } _ := docusaurus_sites[name] or { return false } return true } diff --git a/lib/web/docusaurus/install.v b/lib/web/docusaurus/install.v index 69677a1d..a8f33912 100644 --- a/lib/web/docusaurus/install.v +++ b/lib/web/docusaurus/install.v @@ -6,7 +6,7 @@ import freeflowuniverse.herolib.develop.gittools import freeflowuniverse.herolib.osal.core as osal import freeflowuniverse.herolib.installers.web.bun -fn install( c DocusaurusConfig) ! { +fn install(c DocusaurusConfig) ! { mut gs := gittools.new()! if c.reset { @@ -22,7 +22,7 @@ fn install( c DocusaurusConfig) ! { mut template_path0 := pathlib.get_dir(path: template_path, create: false)! - template_path0.copy(dest: c.path_build.path, delete: false)! //the dir has already been deleted so no point to delete again + template_path0.copy(dest: c.path_build.path, delete: false)! // the dir has already been deleted so no point to delete again // install bun mut installer := bun.get()! @@ -36,5 +36,4 @@ fn install( c DocusaurusConfig) ! { bun install ' )! - } diff --git a/lib/web/docusaurus/play.v b/lib/web/docusaurus/play.v index 8c721e45..fe2c5cdd 100644 --- a/lib/web/docusaurus/play.v +++ b/lib/web/docusaurus/play.v @@ -9,7 +9,7 @@ pub fn play(mut plbook PlayBook) ! { return } - //there should be 1 define section + // there should be 1 define section mut action_define := plbook.ensure_once(filter: 'docusaurus.define')! mut param_define := action_define.params @@ -24,10 +24,9 @@ pub fn play(mut plbook PlayBook) ! { site_name := param_define.get('name') or { return error('In docusaurus.define, param "name" is required.') } - + dsite_define(site_name)! - action_define.done = true mut dsite := dsite_get(site_name)! @@ -47,7 +46,6 @@ pub fn play(mut plbook PlayBook) ! { action.done = true } - mut actions_build := plbook.find(filter: 'docusaurus.build')! if actions_build.len > 1 { return error('Multiple "docusaurus.build" actions found. Only one is allowed.') diff --git a/lib/web/docusaurus/watcher.v b/lib/web/docusaurus/watcher.v index 65166b34..64d0a688 100644 --- a/lib/web/docusaurus/watcher.v +++ b/lib/web/docusaurus/watcher.v @@ -1,6 +1,6 @@ module docusaurus -//not longer working because is coming from doctree +// not longer working because is coming from doctree // import freeflowuniverse.herolib.osal.notifier // import os @@ -97,9 +97,6 @@ module docusaurus // } // } - - - // pub fn (mut s DocSite) dev_watch(args DevArgs) ! { // s.generate()! diff --git a/lib/web/site/play.v b/lib/web/site/play.v index 6c626dfa..637c4f92 100644 --- a/lib/web/site/play.v +++ b/lib/web/site/play.v @@ -1,4 +1,5 @@ module site + import os import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.core.texttools @@ -73,7 +74,7 @@ fn play_import(mut plbook PlayBook, mut config SiteConfig) ! { mut importpath := p.get_default('path', '')! if importpath != '' { - if ! importpath.starts_with('/') { + if !importpath.starts_with('/') { importpath = os.abs_path('${plbook.path}/${importpath}') } } @@ -182,7 +183,7 @@ fn play_publish(mut plbook PlayBook, mut config SiteConfig) ! { for mut action in build_dest_actions { mut p := action.params mut dest := BuildDest{ - path: p.get_default('path', '')! //can be url + path: p.get_default('path', '')! // can be url ssh_name: p.get_default('ssh_name', '')! } config.build_dest << dest @@ -190,13 +191,12 @@ fn play_publish(mut plbook PlayBook, mut config SiteConfig) ! { } } - fn play_publish_dev(mut plbook PlayBook, mut config SiteConfig) ! { mut build_dest_actions := plbook.find(filter: 'site.publish_dev')! for mut action in build_dest_actions { mut p := action.params mut dest := BuildDest{ - path: p.get_default('path', '')! //can be url + path: p.get_default('path', '')! // can be url ssh_name: p.get_default('ssh_name', '')! } config.build_dest_dev << dest diff --git a/lib/installers/infra/dify/.heroscript b/libarchive/dify/.heroscript similarity index 100% rename from lib/installers/infra/dify/.heroscript rename to libarchive/dify/.heroscript diff --git a/lib/installers/infra/dify/dify_actions.v b/libarchive/dify/dify_actions.v similarity index 96% rename from lib/installers/infra/dify/dify_actions.v rename to libarchive/dify/dify_actions.v index 5c39b825..62bb0d51 100644 --- a/lib/installers/infra/dify/dify_actions.v +++ b/libarchive/dify/dify_actions.v @@ -5,7 +5,7 @@ import freeflowuniverse.herolib.ui.console import freeflowuniverse.herolib.core.texttools import freeflowuniverse.herolib.core.pathlib import freeflowuniverse.herolib.osal.systemd -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager import freeflowuniverse.herolib.installers.ulist import freeflowuniverse.herolib.installers.lang.golang import freeflowuniverse.herolib.installers.lang.rust @@ -17,7 +17,8 @@ fn startupcmd() ![]zinit.ZProcessNewArgs { mut installer := get()! mut res := []zinit.ZProcessNewArgs{} mut cfg := get()! - res << zinit.ZProcessNewArgs{ + res << zinit.ZProcessNewArgs + { name: 'docker' cmd: 'dockerd' startuptype: .systemd @@ -31,11 +32,13 @@ fn startupcmd() ![]zinit.ZProcessNewArgs { cd ${cfg.path}/docker/ docker compose --env-file ${cfg.path}/docker/.env up " - res << zinit.ZProcessNewArgs{ + res << zinit.ZProcessNewArgs + { name: 'dify' cmd: cmd startuptype: .systemd } + return res } diff --git a/lib/installers/infra/dify/dify_factory_.v b/libarchive/dify/dify_factory_.v similarity index 65% rename from lib/installers/infra/dify/dify_factory_.v rename to libarchive/dify/dify_factory_.v index 4379b5a8..14123845 100644 --- a/lib/installers/infra/dify/dify_factory_.v +++ b/libarchive/dify/dify_factory_.v @@ -3,75 +3,106 @@ module dify import freeflowuniverse.herolib.core.base import freeflowuniverse.herolib.core.playbook { PlayBook } import freeflowuniverse.herolib.ui.console +import json import freeflowuniverse.herolib.osal.startupmanager -import freeflowuniverse.herolib.osal.zinit import time -__global ( - dify_global map[string]&DifyInstaller - dify_default string -) - /////////FACTORY @[params] pub struct ArgsGet { pub mut: - name string + name string = 'default' + fromdb bool // will load from filesystem + create bool // default will not create if not exist } -fn args_get(args_ ArgsGet) ArgsGet { - mut args := args_ - if args.name == '' { - args.name = 'default' - } - return args -} - -pub fn get(args_ ArgsGet) !&DifyInstaller { - mut context := base.context()! - mut args := args_get(args_) +pub fn new(args ArgsGet) !&DifyInstaller { mut obj := DifyInstaller{ name: args.name } - if args.name !in dify_global { - if !exists(args)! { - set(obj)! + set(obj)! + return &obj +} + +pub fn get(args ArgsGet) !&DifyInstaller { + mut context := base.context()! + dify_default = args.name + if args.fromdb || args.name !in dify_global { + mut r := context.redis()! + if r.hexists('context:dify', args.name)! { + data := r.hget('context:dify', args.name)! + if data.len == 0 { + return error('DifyInstaller with name: dify does not exist, prob bug.') + } + mut obj := json.decode(DifyInstaller, data)! + set_in_mem(obj)! } else { - heroscript := context.hero_config_get('dify', args.name)! - mut obj_ := heroscript_loads(heroscript)! - set_in_mem(obj_)! + if args.create { + new(args)! + } else { + return error("DifyInstaller with name 'dify' does not exist") + } } + return get(name: args.name)! // no longer from db nor create } return dify_global[args.name] or { - println(dify_global) - // bug if we get here because should be in globals - panic('could not get config for dify with name, is bug:${args.name}') + return error('could not get config for dify with name:dify') } } // register the config for the future pub fn set(o DifyInstaller) ! { set_in_mem(o)! + dify_default = o.name mut context := base.context()! - heroscript := heroscript_dumps(o)! - context.hero_config_set('dify', o.name, heroscript)! + mut r := context.redis()! + r.hset('context:dify', o.name, json.encode(o))! } // does the config exists? -pub fn exists(args_ ArgsGet) !bool { +pub fn exists(args ArgsGet) !bool { mut context := base.context()! - mut args := args_get(args_) - return context.hero_config_exists('dify', args.name) + mut r := context.redis()! + return r.hexists('context:dify', args.name)! } -pub fn delete(args_ ArgsGet) ! { - mut args := args_get(args_) +pub fn delete(args ArgsGet) ! { mut context := base.context()! - context.hero_config_delete('dify', args.name)! - if args.name in dify_global { - // del dify_global[args.name] + mut r := context.redis()! + r.hdel('context:dify', args.name)! +} + +@[params] +pub struct ArgsList { +pub mut: + fromdb bool // will load from filesystem +} + +// if fromdb set: load from filesystem, and not from mem, will also reset what is in mem +pub fn list(args ArgsList) ![]&DifyInstaller { + mut res := []&DifyInstaller{} + mut context := base.context()! + if args.fromdb { + // reset what is in mem + dify_global = map[string]&DifyInstaller{} + dify_default = '' } + if args.fromdb { + mut r := context.redis()! + mut l := r.hkeys('context:dify')! + + for name in l { + res << get(name: name, fromdb: true)! + } + return res + } else { + // load from memory + for _, client in dify_global { + res << client + } + } + return res } // only sets in mem, does not set as config @@ -82,6 +113,9 @@ fn set_in_mem(o DifyInstaller) ! { } pub fn play(mut plbook PlayBook) ! { + if !plbook.exists(filter: 'dify.') { + return + } mut install_actions := plbook.find(filter: 'dify.configure')! if install_actions.len > 0 { for install_action in install_actions { @@ -90,7 +124,6 @@ pub fn play(mut plbook PlayBook) ! { set(obj2)! } } - mut other_actions := plbook.find(filter: 'dify.')! for other_action in other_actions { if other_action.name in ['destroy', 'install', 'build'] { @@ -131,36 +164,38 @@ pub fn play(mut plbook PlayBook) ! { //////////////////////////# LIVE CYCLE MANAGEMENT FOR INSTALLERS /////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// -fn startupmanager_get(cat zinit.StartupManagerType) !startupmanager.StartupManager { +fn startupmanager_get(cat startupmanager.StartupManagerType) !startupmanager.StartupManager { // unknown // screen // zinit // tmux // systemd match cat { + .screen { + console.print_debug('startupmanager: zinit') + return startupmanager.get(.screen)! + } .zinit { console.print_debug('startupmanager: zinit') - return startupmanager.get(cat: .zinit)! + return startupmanager.get(.zinit)! } .systemd { console.print_debug('startupmanager: systemd') - return startupmanager.get(cat: .systemd)! + return startupmanager.get(.systemd)! } else { console.print_debug('startupmanager: auto') - return startupmanager.get()! + return startupmanager.get(.auto)! } } } // load from disk and make sure is properly intialized pub fn (mut self DifyInstaller) reload() ! { - switch(self.name) self = obj_init(self)! } pub fn (mut self DifyInstaller) start() ! { - switch(self.name) if self.running()! { return } @@ -223,10 +258,12 @@ pub fn (mut self DifyInstaller) running() !bool { // walk over the generic processes, if not running return for zprocess in startupcmd()! { - mut sm := startupmanager_get(zprocess.startuptype)! - r := sm.running(zprocess.name)! - if r == false { - return false + if zprocess.startuptype != .screen { + mut sm := startupmanager_get(zprocess.startuptype)! + r := sm.running(zprocess.name)! + if r == false { + return false + } } } return running()! @@ -258,12 +295,4 @@ pub fn (mut self DifyInstaller) destroy() ! { // switch instance to be used for dify pub fn switch(name string) { - dify_default = name -} - -// helpers - -@[params] -pub struct DefaultConfigArgs { - instance string = 'default' } diff --git a/lib/installers/infra/dify/dify_model.v b/libarchive/dify/dify_model.v similarity index 100% rename from lib/installers/infra/dify/dify_model.v rename to libarchive/dify/dify_model.v diff --git a/lib/installers/infra/dify/readme.md b/libarchive/dify/readme.md similarity index 100% rename from lib/installers/infra/dify/readme.md rename to libarchive/dify/readme.md diff --git a/lib/installers/infra/dify/templates/atemplate.yaml b/libarchive/dify/templates/atemplate.yaml similarity index 100% rename from lib/installers/infra/dify/templates/atemplate.yaml rename to libarchive/dify/templates/atemplate.yaml diff --git a/lib/clients/zinit_rpc/openrpc.json b/libarchive/zinit/openrpc.json similarity index 100% rename from lib/clients/zinit_rpc/openrpc.json rename to libarchive/zinit/openrpc.json diff --git a/lib/osal/zinit/readme.md b/libarchive/zinit/readme.md similarity index 94% rename from lib/osal/zinit/readme.md rename to libarchive/zinit/readme.md index eefe4c4a..679f4315 100644 --- a/lib/osal/zinit/readme.md +++ b/libarchive/zinit/readme.md @@ -22,7 +22,7 @@ Zinit is a process manager that allows you to manage services on a system. This ### Basic Usage ```v -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager fn main() { // Create a new Zinit client with the default socket path @@ -47,7 +47,7 @@ fn main() { ### Creating a New Service ```v -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager fn main() { mut zinit_client := zinit.new_stateless()! @@ -77,7 +77,7 @@ fn main() { ### Getting Service Statistics ```v -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager fn main() { mut zinit_client := zinit.new_stateless()! @@ -98,7 +98,7 @@ fn main() { ### Retrieving Logs ```v -import freeflowuniverse.herolib.osal.zinit +import freeflowuniverse.herolib.osal.startupmanager fn main() { mut zinit_client := zinit.new_stateless()! diff --git a/lib/osal/zinit/rpc.v b/libarchive/zinit/rpc.v similarity index 100% rename from lib/osal/zinit/rpc.v rename to libarchive/zinit/rpc.v diff --git a/lib/osal/zinit/rpc_test.v b/libarchive/zinit/rpc_test.v similarity index 100% rename from lib/osal/zinit/rpc_test.v rename to libarchive/zinit/rpc_test.v diff --git a/lib/osal/zinit/zinit.v b/libarchive/zinit/zinit.v similarity index 88% rename from lib/osal/zinit/zinit.v rename to libarchive/zinit/zinit.v index 03d46bc4..5f8e7f69 100644 --- a/lib/osal/zinit/zinit.v +++ b/libarchive/zinit/zinit.v @@ -15,6 +15,21 @@ pub mut: pathcmds pathlib.Path } +@[params] +pub struct ZProcessNewArgs { +pub mut: + name string @[required] + cmd string @[required] + cmd_stop string // command to stop (optional) + cmd_test string // command line to test service is running + workdir string // where to execute the commands + after []string // list of service we depend on + env map[string]string + oneshot bool + start bool = true + restart bool = true // whether the process should be restarted on failure +} + // will delete the process if it exists while starting pub fn (mut zinit Zinit) new(args_ ZProcessNewArgs) !ZProcess { console.print_header(' zinit process new') diff --git a/lib/osal/zinit/zinit/service_1.yaml b/libarchive/zinit/zinit/service_1.yaml similarity index 100% rename from lib/osal/zinit/zinit/service_1.yaml rename to libarchive/zinit/zinit/service_1.yaml diff --git a/lib/osal/zinit/zinit/service_2.yaml b/libarchive/zinit/zinit/service_2.yaml similarity index 100% rename from lib/osal/zinit/zinit/service_2.yaml rename to libarchive/zinit/zinit/service_2.yaml diff --git a/lib/osal/zinit/zinit_client.v b/libarchive/zinit/zinit_client.v similarity index 100% rename from lib/osal/zinit/zinit_client.v rename to libarchive/zinit/zinit_client.v diff --git a/lib/osal/zinit/zinit_factory.v b/libarchive/zinit/zinit_factory.v similarity index 100% rename from lib/osal/zinit/zinit_factory.v rename to libarchive/zinit/zinit_factory.v diff --git a/lib/osal/zinit/zinit_openrpc_test.v b/libarchive/zinit/zinit_openrpc_test.v similarity index 100% rename from lib/osal/zinit/zinit_openrpc_test.v rename to libarchive/zinit/zinit_openrpc_test.v diff --git a/lib/osal/zinit/zinit_stateless.v b/libarchive/zinit/zinit_stateless.v similarity index 100% rename from lib/osal/zinit/zinit_stateless.v rename to libarchive/zinit/zinit_stateless.v diff --git a/lib/osal/zinit/zprocess.v b/libarchive/zinit/zprocess.v similarity index 90% rename from lib/osal/zinit/zprocess.v rename to libarchive/zinit/zprocess.v index f4760de0..2690e3dd 100644 --- a/lib/osal/zinit/zprocess.v +++ b/libarchive/zinit/zprocess.v @@ -34,30 +34,6 @@ pub enum ZProcessStatus { spawned } -pub enum StartupManagerType { - unknown - zinit - systemd - screen -} - -@[params] -pub struct ZProcessNewArgs { -pub mut: - name string @[required] - cmd string @[required] - cmd_stop string // command to stop (optional) - cmd_test string // command line to test service is running - workdir string // where to execute the commands - after []string // list of service we depend on - env map[string]string - oneshot bool - start bool = true - restart bool = true // whether the process should be restarted on failure - description string // not used in zinit - startuptype StartupManagerType -} - pub fn (zp ZProcess) cmd() !string { mut zinitobj := new()! mut path := zinitobj.pathcmds.file_get_new('${zp.name}_start.sh')! diff --git a/lib/osal/zinit/zprocess_load.v b/libarchive/zinit/zprocess_load.v similarity index 100% rename from lib/osal/zinit/zprocess_load.v rename to libarchive/zinit/zprocess_load.v