diff --git a/.github/tips.md b/.github/README.md similarity index 60% rename from .github/tips.md rename to .github/README.md index 59e7d490b5e..8c7c3770c5a 100644 --- a/.github/tips.md +++ b/.github/README.md @@ -7,3 +7,15 @@ WARNING: do not name this file as README.md since it will be rendered in the mai - ``PULL_REQUEST_TEMPLATE.md`` - ``ISSUE_TEMPLATE`` folder: - Add [top-level syntax](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-issue-forms#top-level-syntax) to ISSUE_TEMPLATE/*.md files to configure them and view them as [template in the github web](https://github.com/ITISFoundation/osparc-simcore/issues/new/choose) + + +--- + +## Copilot Usage Tips + +1. **Be Specific**: Provide clear and detailed prompts to Copilot for better suggestions. +2. **Iterate**: Review and refine Copilot's suggestions to ensure they meet project standards. +3. **Split Tasks**: Break down complex tasks into smaller, manageable parts for better suggestions. +4. **Test Suggestions**: Always test Copilot-generated code to ensure it works as expected. + +- SEE https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a1293f80e9d..d3dfe8af322 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -4,52 +4,58 @@ This document provides guidelines and best practices for using GitHub Copilot in ## General Guidelines -1. **Use Python 3.11**: Ensure that all Python-related suggestions align with Python 3.11 features and syntax. -2. **Node.js Compatibility**: For Node.js projects, ensure compatibility with the version specified in the project (e.g., Node.js 14 or later). -3. **Follow Coding Conventions**: Adhere to the coding conventions outlined in the `docs/coding-conventions.md` file. -4. **Test-Driven Development**: Write unit tests for all new functions and features. Use `pytest` for Python and appropriate testing frameworks for Node.js. -5. **Environment Variables**: Use environment variables as specified in `docs/env-vars.md` for configuration. Avoid hardcoding sensitive information. -6. **Documentation**: Prefer self-explanatory code; add documentation only if explicitly requested by the developer. +1. **Test-Driven Development**: Write unit tests for all new functions and features. Use `pytest` for Python and appropriate testing frameworks for Node.js. +2. **Environment Variables**: Use [Environment Variables Guide](../docs/env-vars.md) for configuration. Avoid hardcoding sensitive information. +3. **Documentation**: Prefer self-explanatory code; add documentation only if explicitly requested by the developer. -## Python-Specific Instructions +--- -- Always use type hints and annotations to improve code clarity and compatibility with tools like `mypy`. - - An exception to that rule is in `test_*` functions return type hint must not be added -- Follow the dependency management practices outlined in `requirements/`. -- Use `ruff` for code formatting and for linting. -- Use `black` for code formatting and `pylint` for linting. -- ensure we use `sqlalchemy` >2 compatible code. -- ensure we use `pydantic` >2 compatible code. -- ensure we use `fastapi` >0.100 compatible code -- use f-string formatting -- Only add comments in function if strictly necessary -- use relative imports -- imports should be at top of the file +## 🛠️Coding Instructions for Python in This Repository +Follow these rules strictly when generating Python code: -### Json serialization +### 1. Python Version -- Generally use `json_dumps`/`json_loads` from `common_library.json_serialization` to built-in `json.dumps` / `json.loads`. -- Prefer Pydantic model methods (e.g., `model.model_dump_json()`) for serialization. +* Use Python 3.11: Ensure all code uses features and syntax compatible with Python 3.11. +### 2. **Type Annotations** -## Node.js-Specific Instructions +* Always use full type annotations for all functions and class attributes. +* ❗ **Exception**: Do **not** add return type annotations in `test_*` functions. -- Use ES6+ syntax and features. -- Follow the `package.json` configuration for dependencies and scripts. -- Use `eslint` for linting and `prettier` for code formatting. -- Write modular and reusable code, adhering to the project's structure. +### 3. **Code Style & Formatting** -## Copilot Usage Tips +* Follow [Python Coding Conventions](../docs/coding-conventions.md) **strictly**. +* Format code with `black`. +* Lint code with `ruff` and `pylint`. -1. **Be Specific**: Provide clear and detailed prompts to Copilot for better suggestions. -2. **Iterate**: Review and refine Copilot's suggestions to ensure they meet project standards. -3. **Split Tasks**: Break down complex tasks into smaller, manageable parts for better suggestions. -4. **Test Suggestions**: Always test Copilot-generated code to ensure it works as expected. +### 4. **Library Compatibility** -## Additional Resources +Ensure compatibility with the following library versions: -- [Python Coding Conventions](../docs/coding-conventions.md) -- [Environment Variables Guide](../docs/env-vars.md) -- [Pydantic Annotated fields](../docs/llm-prompts/pydantic-annotated-fields.md) -- [Steps to Upgrade Python](../docs/steps-to-upgrade-python.md) +* `sqlalchemy` ≥ 2.x +* `pydantic` ≥ 2.x +* `fastapi` ≥ 0.100 + + +### 5. **Code Practices** + +* Use `f-string` formatting for all string interpolation except for logging message strings. +* Use **relative imports** within the same package/module. +* Place **all imports at the top** of the file. +* Add comments **only when the code is not self-explanatory**. + + +### 6. **JSON Serialization** + +* Prefer `json_dumps` / `json_loads` from `common_library.json_serialization` instead of the built-in `json.dumps` / `json.loads`. +* When using Pydantic models, prefer methods like `model.model_dump_json()` for serialization. + +--- + +## 🛠️Coding Instructions for Node.js in This Repository + +* Use ES6+ syntax and features. +* Follow the `package.json` configuration for dependencies and scripts. +* Use `eslint` for linting and `prettier` for code formatting. +* Write modular and reusable code, adhering to the project's structure. diff --git a/docs/llm-prompts/pydantic-annotated-fields.md b/.github/prompts/pydantic-annotated-fields.prompt.md similarity index 89% rename from docs/llm-prompts/pydantic-annotated-fields.md rename to .github/prompts/pydantic-annotated-fields.prompt.md index 9b128e7bd72..cb2c24b851d 100644 --- a/docs/llm-prompts/pydantic-annotated-fields.md +++ b/.github/prompts/pydantic-annotated-fields.prompt.md @@ -1,6 +1,9 @@ -# Prompt +--- +mode: 'edit' +description: 'Convert Pydantic model fields to use Annotated pattern' +--- + -``` Please convert all pydantic model fields that use `Field()` with default values to use the Annotated pattern instead. Follow these guidelines: @@ -10,7 +13,8 @@ Follow these guidelines: 4. Add the import: `from common_library.basic_types import DEFAULT_FACTORY` if it's not already present. 5. If `Field()` has no parameters (empty), don't use Annotated at all. Just use: `field_name: field_type = default_value`. 6. Leave any model validations, `model_config` settings, and `field_validators` untouched. -``` + + ## Examples ### Before: @@ -53,13 +57,11 @@ class ProjectModel(BaseModel): id: str = Field(default_factory=uuid.uuid4, description="Unique project identifier") name: str = Field(default="Untitled Project", min_length=3, max_length=50) created_at: datetime = Field(default_factory=datetime.now) + value: int = Field(..., description="Project value") + str_with_default: str = Field(default="foo") + config: dict = Field(default={"version": "1.0", "theme": "default"}) - @field_validator("name") - def validate_name(cls, v): - if v.isdigit(): - raise ValueError("Name cannot be only digits") - return v ``` ### After: @@ -74,11 +76,9 @@ class ProjectModel(BaseModel): id: Annotated[str, Field(default_factory=uuid.uuid4, description="Unique project identifier")] = DEFAULT_FACTORY name: Annotated[str, Field(min_length=3, max_length=50)] = "Untitled Project" created_at: Annotated[datetime, Field(default_factory=datetime.now)] = DEFAULT_FACTORY + value: Annotated[int, Field(description="Project value")] + str_with_default: str = "foo" + config: dict = {"version": "1.0", "theme": "default"} - @field_validator("name") - def validate_name(cls, v): - if v.isdigit(): - raise ValueError("Name cannot be only digits") - return v ``` diff --git a/services/web/server/src/simcore_service_webserver/diagnostics/_handlers.py b/services/web/server/src/simcore_service_webserver/diagnostics/_handlers.py index a25b1442d65..f715e09b248 100644 --- a/services/web/server/src/simcore_service_webserver/diagnostics/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/diagnostics/_handlers.py @@ -7,7 +7,7 @@ from aiohttp import ClientError, ClientSession, web from models_library.app_diagnostics import AppStatusCheck -from pydantic import BaseModel, Field +from pydantic import BaseModel from servicelib.aiohttp.client_session import get_client_session from servicelib.aiohttp.requests_validation import parse_request_query_parameters_as from servicelib.utils import logged_gather @@ -29,7 +29,7 @@ class StatusDiagnosticsQueryParam(BaseModel): - top_tracemalloc: int | None = Field(default=None) + top_tracemalloc: int | None = None class StatusDiagnosticsGet(BaseModel): diff --git a/services/web/server/src/simcore_service_webserver/exporter/_formatter/xlsx/code_description.py b/services/web/server/src/simcore_service_webserver/exporter/_formatter/xlsx/code_description.py index 15ee7bac3b0..e53142df73d 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/_formatter/xlsx/code_description.py +++ b/services/web/server/src/simcore_service_webserver/exporter/_formatter/xlsx/code_description.py @@ -1,6 +1,7 @@ from abc import abstractmethod -from typing import Any, ClassVar, Final, cast +from typing import Annotated, Any, ClassVar, Final, cast +from common_library.basic_types import DEFAULT_FACTORY from models_library.services import ServiceKey, ServiceVersion from pydantic import BaseModel, Field, StrictStr @@ -10,20 +11,23 @@ class RRIDEntry(BaseModel): - rrid_term: StrictStr = Field(..., description="Associated tools or resources used") - rrid_identifier: StrictStr = Field( - ..., description="Associated tools or resources identifier (with 'RRID:')" - ) + rrid_term: Annotated[ + StrictStr, Field(description="Associated tools or resources used") + ] + rrid_identifier: Annotated[ + StrictStr, + Field(description="Associated tools or resources identifier (with 'RRID:')"), + ] # the 2 items below are not enabled for now - ontological_term: StrictStr = Field( - "", description="Associated ontological term (human-readable)" - ) - ontological_identifier: StrictStr = Field( - "", - description=( - "Associated ontological identifier from SciCrunch https://scicrunch.org/sawg" + ontological_term: Annotated[ + StrictStr, Field(description="Associated ontological term (human-readable)") + ] = "" + ontological_identifier: Annotated[ + StrictStr, + Field( + description="Associated ontological identifier from SciCrunch https://scicrunch.org/sawg" ), - ) + ] = "" class TSREntry(BaseModel): @@ -33,99 +37,117 @@ class TSREntry(BaseModel): class CodeDescriptionModel(BaseModel): - rrid_entires: list[RRIDEntry] = Field( - default_factory=list, description="composed from the classifiers" - ) + rrid_entires: Annotated[ + list[RRIDEntry], + Field(default_factory=list, description="composed from the classifiers"), + ] = DEFAULT_FACTORY # TSR - tsr_entries: dict[str, TSREntry] = Field( - default_factory=dict, description="list of rules to generate tsr" - ) + tsr_entries: Annotated[ + dict[str, TSREntry], + Field(default_factory=dict, description="list of rules to generate tsr"), + ] = DEFAULT_FACTORY class InputsEntryModel(BaseModel): - service_alias: StrictStr = Field( - ..., description="Name of the service containing this input, given by the user" - ) - service_name: StrictStr = Field( - ..., description="Name of the service containing this input" - ) - service_key: ServiceKey = Field( - ..., description="Key of the service containing this input" - ) - service_version: ServiceVersion = Field( - ..., description="Version of the service containing this input" - ) - input_name: StrictStr = Field( - "", description="An input field to the MSoP submission" - ) - input_parameter_description: StrictStr = Field( - "", description="Description of what the parameter represents" - ) - input_data_type: StrictStr = Field( - "", description="Data type for the input field (in plain text)" - ) - input_data_units: StrictStr = Field( - "", description="Units of data for the input field, if applicable" - ) - input_data_default_value: StrictStr = Field( - "", - description="Default value for the input field, if applicable (doi or value)", - ) - input_data_constraints: StrictStr = Field( - "", - description="Range [min, max] of acceptable parameter values, or other constraints as formulas / sets", - ) + service_alias: Annotated[ + StrictStr, + Field( + description="Name of the service containing this input, given by the user" + ), + ] + service_name: Annotated[ + StrictStr, Field(description="Name of the service containing this input") + ] + service_key: Annotated[ + ServiceKey, Field(description="Key of the service containing this input") + ] + service_version: Annotated[ + ServiceVersion, + Field(description="Version of the service containing this input"), + ] + input_name: Annotated[ + StrictStr, Field(description="An input field to the MSoP submission") + ] = "" + input_parameter_description: Annotated[ + StrictStr, Field(description="Description of what the parameter represents") + ] = "" + input_data_type: Annotated[ + StrictStr, Field(description="Data type for the input field (in plain text)") + ] = "" + input_data_units: Annotated[ + StrictStr, Field(description="Units of data for the input field, if applicable") + ] = "" + input_data_default_value: Annotated[ + StrictStr, + Field( + description="Default value for the input field, if applicable (doi or value)" + ), + ] = "" + input_data_constraints: Annotated[ + StrictStr, + Field( + description="Range [min, max] of acceptable parameter values, or other constraints as formulas / sets" + ), + ] = "" class OutputsEntryModel(BaseModel): - service_alias: StrictStr = Field( - ..., description="Name of the service producing this output, given by the user" - ) - service_name: StrictStr = Field( - ..., description="Name of the service containing this output" - ) - service_key: ServiceKey = Field( - ..., description="Key of the service containing this output" - ) - service_version: ServiceVersion = Field( - ..., description="Version of the service containing this output" - ) - output_name: StrictStr = Field( - "", description="An output field to the MSoP submission" - ) - output_parameter_description: StrictStr = Field( - "", description="Description of what the parameter represents" - ) - output_data_ontology_identifier: StrictStr = Field( - "", - description=( - "Ontology identifier for the input field, if applicable , " - "https://scicrunch.org/scicrunch/interlex/search?q=NLXOEN&l=NLXOEN&types=term" + service_alias: Annotated[ + StrictStr, + Field( + description="Name of the service producing this output, given by the user" ), - ) - output_data_type: StrictStr = Field( - "", description="Data type for the output field" - ) - output_data_units: StrictStr = Field( - "", description="Units of data for the output field, if applicable" - ) - output_data_constraints: StrictStr = Field( - "", - description="Range [min, max] of acceptable parameter values, or other constraints as formulas / sets", - ) + ] + service_name: Annotated[ + StrictStr, Field(description="Name of the service containing this output") + ] + service_key: Annotated[ + ServiceKey, Field(description="Key of the service containing this output") + ] + service_version: Annotated[ + ServiceVersion, + Field(description="Version of the service containing this output"), + ] + output_name: Annotated[ + StrictStr, Field(description="An output field to the MSoP submission") + ] = "" + output_parameter_description: Annotated[ + StrictStr, Field(description="Description of what the parameter represents") + ] = "" + output_data_ontology_identifier: Annotated[ + StrictStr, + Field( + description="Ontology identifier for the input field, if applicable , https://scicrunch.org/scicrunch/interlex/search?q=NLXOEN&l=NLXOEN&types=term" + ), + ] = "" + output_data_type: Annotated[ + StrictStr, Field(description="Data type for the output field") + ] = "" + output_data_units: Annotated[ + StrictStr, + Field(description="Units of data for the output field, if applicable"), + ] = "" + output_data_constraints: Annotated[ + StrictStr, + Field( + description="Range [min, max] of acceptable parameter values, or other constraints as formulas / sets" + ), + ] = "" class CodeDescriptionParams(BaseModel): - code_description: CodeDescriptionModel = Field( - ..., description="code description data" - ) - inputs: list[InputsEntryModel] = Field( - default_factory=list, description="List of inputs, if any" - ) - outputs: list[OutputsEntryModel] = Field( - default_factory=list, description="List of outputs, if any" - ) + code_description: Annotated[ + CodeDescriptionModel, Field(description="code description data") + ] + inputs: Annotated[ + list[InputsEntryModel], + Field(default_factory=list, description="List of inputs, if any"), + ] = DEFAULT_FACTORY + outputs: Annotated[ + list[OutputsEntryModel], + Field(default_factory=list, description="List of outputs, if any"), + ] = DEFAULT_FACTORY def _include_ports_from_this_service(service_key: ServiceKey) -> bool: