diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..f7539d5 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,59 @@ +name: Deploy API Docs to GitHub Pages + +on: + push: + branches: [ main ] + workflow_dispatch: + +jobs: + build-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Copia README.md para docs/ + run: tail -n +2 README.md >> docs/readme.md + + - name: Disable Jekyll + run: touch docs/.nojekyll + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Python dependencies + run: pip install -r requirements.txt + + - name: Export OpenAPI schema + env: + SECRET_KEY: dummy + ALGORITHM: HS256 + ACCESS_TOKEN_EXPIRE_MINUTES: 20 + SQLITE_PATH: /tmp/pynewsdb.db + SQLITE_URL: sqlite+aiosqlite:////tmp/pynewsdb.db + PYTHONPATH: . + run: python scripts/export_openapi.py app.main:app docs/openapi.json + + - name: Build Sphinx docs (com Redoc embutido) + run: sphinx-build -b html docs/ docs/_build/html + + - name: Upload built site to Pages + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_build/html + + deploy: + needs: build-docs + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..5ed658e --- /dev/null +++ b/docs/api.md @@ -0,0 +1,7 @@ +# API + +Abaixo está a especificação OpenAPI do PyNewsServer: + +```{redoc} +../openapi.json +``` diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..9e3042a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,45 @@ +# -- Project information ----------------------------------------------------- +project = "pynewsserver" +author = "PyNews" + +# -- General configuration --------------------------------------------------- +extensions = [ + "myst_parser", + "sphinx_rtd_theme", + "sphinxcontrib.redoc", + "sphinx_copybutton", +] + +myst_enable_extensions = [ + "colon_fence", + "deflist", + "linkify", + "substitution", +] + +source_suffix = { + ".rst": "restructuredtext", + ".md": "markdown", +} + +master_doc = "index" + +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# -- Options for HTML output ------------------------------------------------- +html_theme = "sphinx_rtd_theme" +html_title = "Documentação - PyNewsServer" +html_static_path = ['_static'] + +# -- Options for sphinxcontrib-redoc ----------------------------------------- +redoc = [ + { + "name": "OpenAPI", + "page": "api", + "spec": "../openapi.json", + "embed": True, + "opts": { + "hide-hostname": True + } + }, +] diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..220003c --- /dev/null +++ b/docs/index.md @@ -0,0 +1,9 @@ +# Documentação do PyNewsServer + +```{toctree} +:maxdepth: 2 +:caption: Sumário + +readme +api +``` diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..fb965e9 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,2 @@ +# Documentação do PyNewsServer + diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..4dd5ab5 --- /dev/null +++ b/openapi.json @@ -0,0 +1,1092 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "pynews-server", + "description": "PyNews Server", + "version": "0.1.0" + }, + "paths": { + "/api/healthcheck": { + "get": { + "tags": [ + "healthcheck" + ], + "summary": "Health check endpoint", + "description": "Returns the health status of the API", + "operationId": "healthcheck_api_healthcheck_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthCheckResponse" + } + } + } + } + } + } + }, + "/api/news": { + "post": { + "tags": [ + "news" + ], + "summary": "News endpoint", + "description": "Creates news and returns a confirmation message", + "operationId": "post_news_api_news_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "user-email", + "in": "header", + "required": true, + "schema": { + "type": "string", + "title": "User-Email" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/News" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsPostResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "get": { + "tags": [ + "news" + ], + "summary": "Get News", + "description": "Retrieves news filtered by user and query params", + "operationId": "get_news_api_news_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "id", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Id" + } + }, + { + "name": "category", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Category" + } + }, + { + "name": "tags", + "in": "query", + "required": false, + "schema": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tags" + } + }, + { + "name": "user-email", + "in": "header", + "required": true, + "schema": { + "type": "string", + "title": "User-Email" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsGetResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/news/{news_id}/like": { + "post": { + "tags": [ + "news" + ], + "summary": "News like endpoint", + "description": "Allows user to like a news item", + "operationId": "post_like_api_news__news_id__like_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "news_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "News Id" + } + }, + { + "name": "user-email", + "in": "header", + "required": true, + "schema": { + "type": "string", + "title": "User-Email" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsLikeResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "news" + ], + "summary": "News undo like endpoint", + "description": "Allows user to undo a like to a news item", + "operationId": "delete_like_api_news__news_id__like_delete", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "news_id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "title": "News Id" + } + }, + { + "name": "user-email", + "in": "header", + "required": true, + "schema": { + "type": "string", + "title": "User-Email" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NewsLikeResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/authentication/create_commumity": { + "post": { + "tags": [ + "authentication" + ], + "summary": "Create Community", + "operationId": "create_community_api_authentication_create_commumity_post", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/api/authentication/token": { + "post": { + "tags": [ + "authentication" + ], + "summary": "Login For Access Token", + "operationId": "login_for_access_token_api_authentication_token_post", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Body_login_for_access_token_api_authentication_token_post" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Token" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/authentication/me": { + "get": { + "tags": [ + "authentication" + ], + "summary": "Read Community Me", + "operationId": "read_community_me_api_authentication_me_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Community" + } + } + } + } + }, + "security": [ + { + "OAuth2PasswordBearer": [] + } + ] + } + }, + "/api/libraries": { + "get": { + "tags": [ + "libraries" + ], + "summary": "Get libraries by language", + "description": "Get libraries by language", + "operationId": "get_by_language_api_libraries_get", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "language", + "in": "query", + "required": true, + "schema": { + "type": "string", + "title": "Language" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Library-Output" + }, + "title": "Response Get By Language Api Libraries Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + }, + "post": { + "tags": [ + "libraries" + ], + "summary": "Create a library", + "description": "Create a new library to follow", + "operationId": "create_library_api_libraries_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Library-Input" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/libraries/subscribe": { + "post": { + "tags": [ + "libraries" + ], + "summary": "Subscribe to receive library updates", + "description": "Subscribe to multiple libs and tags to receive libs updates", + "operationId": "subscribe_libraries_api_libraries_subscribe_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "user-email", + "in": "header", + "required": true, + "schema": { + "type": "string", + "title": "User-Email" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Subscription" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubscribeLibraryResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/api/libraries/request": { + "post": { + "tags": [ + "libraries" + ], + "summary": "Request a library", + "description": "Request a library to follow", + "operationId": "request_library_api_libraries_request_post", + "security": [ + { + "OAuth2PasswordBearer": [] + } + ], + "parameters": [ + { + "name": "user-email", + "in": "header", + "required": true, + "schema": { + "type": "string", + "title": "User-Email" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Body_login_for_access_token_api_authentication_token_post": { + "properties": { + "grant_type": { + "anyOf": [ + { + "type": "string", + "pattern": "^password$" + }, + { + "type": "null" + } + ], + "title": "Grant Type" + }, + "username": { + "type": "string", + "title": "Username" + }, + "password": { + "type": "string", + "format": "password", + "title": "Password" + }, + "scope": { + "type": "string", + "title": "Scope", + "default": "" + }, + "client_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Client Id" + }, + "client_secret": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "format": "password", + "title": "Client Secret" + } + }, + "type": "object", + "required": [ + "username", + "password" + ], + "title": "Body_login_for_access_token_api_authentication_token_post" + }, + "Community": { + "properties": { + "username": { + "type": "string", + "title": "Username" + }, + "email": { + "type": "string", + "title": "Email" + } + }, + "type": "object", + "required": [ + "username", + "email" + ], + "title": "Community" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "HealthCheckResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "default": "healthy" + }, + "version": { + "type": "string", + "title": "Version", + "default": "2.0.0" + } + }, + "type": "object", + "title": "HealthCheckResponse" + }, + "Library-Input": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Id" + }, + "library_name": { + "type": "string", + "title": "Library Name" + }, + "news": { + "items": { + "$ref": "#/components/schemas/LibraryNews" + }, + "type": "array", + "title": "News" + }, + "logo": { + "type": "string", + "title": "Logo" + }, + "version": { + "type": "string", + "title": "Version" + }, + "release_date": { + "type": "string", + "format": "date", + "title": "Release Date" + }, + "releases_doc_url": { + "type": "string", + "title": "Releases Doc Url" + }, + "fixed_release_url": { + "type": "string", + "title": "Fixed Release Url" + }, + "language": { + "type": "string", + "title": "Language" + } + }, + "type": "object", + "required": [ + "library_name", + "news", + "logo", + "version", + "release_date", + "releases_doc_url", + "fixed_release_url", + "language" + ], + "title": "Library" + }, + "Library-Output": { + "properties": { + "id": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Id" + }, + "library_name": { + "type": "string", + "title": "Library Name" + }, + "news": { + "items": { + "$ref": "#/components/schemas/LibraryNews" + }, + "type": "array", + "title": "News" + }, + "logo": { + "type": "string", + "title": "Logo" + }, + "version": { + "type": "string", + "title": "Version" + }, + "release_date": { + "type": "string", + "format": "date", + "title": "Release Date" + }, + "releases_doc_url": { + "type": "string", + "title": "Releases Doc Url" + }, + "fixed_release_url": { + "type": "string", + "title": "Fixed Release Url" + }, + "language": { + "type": "string", + "title": "Language" + } + }, + "type": "object", + "required": [ + "library_name", + "news", + "logo", + "version", + "release_date", + "releases_doc_url", + "fixed_release_url", + "language" + ], + "title": "Library" + }, + "LibraryNews": { + "properties": { + "tag": { + "$ref": "#/components/schemas/LibraryTagUpdatesEnum" + }, + "description": { + "type": "string", + "title": "Description" + } + }, + "type": "object", + "required": [ + "tag", + "description" + ], + "title": "LibraryNews" + }, + "LibraryRequest": { + "properties": { + "library_name": { + "type": "string", + "title": "Library Name" + }, + "library_home_page": { + "type": "string", + "title": "Library Home Page" + } + }, + "type": "object", + "required": [ + "library_name", + "library_home_page" + ], + "title": "LibraryRequest" + }, + "LibraryResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "default": "Library created successfully" + } + }, + "type": "object", + "title": "LibraryResponse" + }, + "LibraryTagUpdatesEnum": { + "type": "string", + "enum": [ + "updates", + "bug_fix", + "new_feature", + "security_fix" + ], + "title": "LibraryTagUpdatesEnum" + }, + "News": { + "properties": { + "title": { + "type": "string", + "title": "Title" + }, + "content": { + "type": "string", + "title": "Content" + }, + "category": { + "type": "string", + "title": "Category" + }, + "tags": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Tags" + }, + "source_url": { + "type": "string", + "title": "Source Url" + }, + "social_media_url": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Social Media Url" + }, + "likes": { + "type": "integer", + "title": "Likes", + "default": 0 + } + }, + "type": "object", + "required": [ + "title", + "content", + "category", + "source_url" + ], + "title": "News" + }, + "NewsGetResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "default": "Lista de News Obtida" + }, + "news_list": { + "items": {}, + "type": "array", + "title": "News List", + "default": [] + } + }, + "type": "object", + "title": "NewsGetResponse" + }, + "NewsLikeResponse": { + "properties": { + "total_likes": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ], + "title": "Total Likes" + } + }, + "type": "object", + "required": [ + "total_likes" + ], + "title": "NewsLikeResponse" + }, + "NewsPostResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "default": "News Criada" + } + }, + "type": "object", + "title": "NewsPostResponse" + }, + "SubscribeLibraryResponse": { + "properties": { + "status": { + "type": "string", + "title": "Status", + "default": "Subscribed in libraries successfully" + } + }, + "type": "object", + "title": "SubscribeLibraryResponse" + }, + "Subscription": { + "properties": { + "tags": { + "items": { + "$ref": "#/components/schemas/LibraryTagUpdatesEnum" + }, + "type": "array", + "title": "Tags" + }, + "libraries_list": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Libraries List" + } + }, + "type": "object", + "required": [ + "tags", + "libraries_list" + ], + "title": "Subscription" + }, + "Token": { + "properties": { + "access_token": { + "type": "string", + "title": "Access Token" + }, + "token_type": { + "type": "string", + "title": "Token Type" + }, + "expires_in": { + "type": "integer", + "title": "Expires In" + } + }, + "type": "object", + "required": [ + "access_token", + "token_type", + "expires_in" + ], + "title": "Token" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + } + }, + "securitySchemes": { + "OAuth2PasswordBearer": { + "type": "oauth2", + "flows": { + "password": { + "scopes": {}, + "tokenUrl": "/authentication/token" + } + } + } + } + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8118010 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,48 @@ +# --- Dependências para actions --- +sphinx>=7.0.0 +myst-parser>=2.0.0 +sphinx-rtd-theme>=2.0.0 +sphinx-copybutton>=0.5.2 +sphinxcontrib-redoc>=1.6.0 +linkify-it-py>=2.0.0 +setuptools>=68.0.0 +# --- Fim dependências actions --- +aiosqlite==0.21.0 ; python_version >= "3.12" and python_version < "4.0" +annotated-types==0.7.0 ; python_version >= "3.12" and python_version < "4.0" +anyio==4.9.0 ; python_version >= "3.12" and python_version < "4.0" +bcrypt==4.3.0 ; python_version >= "3.12" and python_version < "4.0" +certifi==2025.7.14 ; python_version >= "3.12" and python_version < "4.0" +cfgv==3.4.0 ; python_version >= "3.12" and python_version < "4.0" +click==8.2.1 ; python_version >= "3.12" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.12" and python_version < "4.0" and platform_system == "Windows" +deprecated==1.2.18 ; python_version >= "3.12" and python_version < "4.0" +distlib==0.4.0 ; python_version >= "3.12" and python_version < "4.0" +fastapi==0.115.14 ; python_version >= "3.12" and python_version < "4.0" +filelock==3.18.0 ; python_version >= "3.12" and python_version < "4.0" +greenlet==3.2.3 ; python_version >= "3.12" and python_version < "3.14" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32") +h11==0.16.0 ; python_version >= "3.12" and python_version < "4.0" +httpcore==1.0.9 ; python_version >= "3.12" and python_version < "4.0" +httpx==0.28.1 ; python_version >= "3.12" and python_version < "4.0" +identify==2.6.12 ; python_version >= "3.12" and python_version < "4.0" +idna==3.10 ; python_version >= "3.12" and python_version < "4.0" +limits==5.5.0 ; python_version >= "3.12" and python_version < "4.0" +nodeenv==1.9.1 ; python_version >= "3.12" and python_version < "4.0" +orjson==3.10.18 ; python_version >= "3.12" and python_version < "4.0" +packaging==25.0 ; python_version >= "3.12" and python_version < "4.0" +platformdirs==4.3.8 ; python_version >= "3.12" and python_version < "4.0" +pre-commit==4.2.0 ; python_version >= "3.12" and python_version < "4.0" +pydantic-core==2.33.2 ; python_version >= "3.12" and python_version < "4.0" +pydantic==2.11.7 ; python_version >= "3.12" and python_version < "4.0" +pyjwt==2.10.1 ; python_version >= "3.12" and python_version < "4.0" +python-multipart==0.0.20 ; python_version >= "3.12" and python_version < "4.0" +pyyaml==6.0.2 ; python_version >= "3.12" and python_version < "4.0" +slowapi==0.1.9 ; python_version >= "3.12" and python_version < "4.0" +sniffio==1.3.1 ; python_version >= "3.12" and python_version < "4.0" +sqlalchemy==2.0.41 ; python_version >= "3.12" and python_version < "4.0" +sqlmodel==0.0.24 ; python_version >= "3.12" and python_version < "4.0" +starlette==0.46.2 ; python_version >= "3.12" and python_version < "4.0" +typing-extensions==4.14.1 ; python_version >= "3.12" and python_version < "4.0" +typing-inspection==0.4.1 ; python_version >= "3.12" and python_version < "4.0" +uvicorn==0.22.0 ; python_version >= "3.12" and python_version < "4.0" +virtualenv==20.32.0 ; python_version >= "3.12" and python_version < "4.0" +wrapt==1.17.3 ; python_version >= "3.12" and python_version < "4.0" diff --git a/scripts/export_openapi.py b/scripts/export_openapi.py new file mode 100644 index 0000000..8b1728b --- /dev/null +++ b/scripts/export_openapi.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# scripts/export_openapi.py +import importlib +import json +import sys + +def load_app(dotted): + if ':' in dotted: + module_name, attr = dotted.split(':', 1) + else: + module_name, attr = dotted, 'app' + mod = importlib.import_module(module_name) + return getattr(mod, attr) + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Uso: export_openapi.py [output.json]") + sys.exit(2) + module = sys.argv[1] + out = sys.argv[2] if len(sys.argv) > 2 else "openapi.json" + app = load_app(module) + with open(out, "w") as f: + json.dump(app.openapi(), f, indent=2) + print(f"Wrote {out}")