Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add rest config and rest config checker #2432

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

joaoandre-avaiga
Copy link
Collaborator

@joaoandre-avaiga joaoandre-avaiga commented Jan 29, 2025

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

Add option to inform rest server Host, Port and other attributes that could be used for a deploy in a production environment.

Related Tickets & Documents

Checklist

We encourage you to keep the code coverage percentage at 80% and above.

  • This solution meets the acceptance criteria of the related issue.
  • The related issue checklist is completed.
  • This PR adds unit tests for the developed code. If not, why?
  • End-to-End tests have been added or updated. If not, why?
  • The documentation has been updated, or a dedicated issue created. (If applicable)
  • The release notes have been updated? (If applicable)

@joaoandre-avaiga joaoandre-avaiga force-pushed the feature/#421-support-rest-production-env branch from b620c8c to e7536bf Compare January 29, 2025 00:57
Copy link
Member

@jrobinAV jrobinAV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some refactoring is needed. You can have a look at the taipy/core/config/__init__.py and taipy/core/config/core_section.py files

from taipy.common.config import Section, _inject_section


class RestConfig(Section):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be a unique section, no? Only one RestConfig should be created in the Config. No?

def ssl_context(self) -> Optional[Tuple[Optional[str], Optional[str]]]:
return (self._ssl_cert, self._ssl_key) if self._use_https else None

def configure_rest(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use the same design pattern as the existing section implementations for legacy and consistency, even if your proposal seems smoother. Changing the design pattern could be a good idea (the factory pattern can be cumbersome), but it requires some deep discussions before, as it is an important breaking change.

For the other sections, we reserve the __init__ constructors for Taipy's internal usage. It is indirectly used by serializers (TOML and JSON) that need to instantiate sections from files (eventually with some missing attributes). So they expose all the parameters as optional. On the contrary, the _configure method is a static method acting as a factory, so the developer doesn't need to use the constructor directly (__init__ method). It instantiates the Section, registers it in the Config, and returns it.

Something like:

    @staticmethod
    def _configure(
        port: int = 5000,
        host: str = "127.0.0.1",
        use_https: bool = False,
        ssl_cert: Optional[str] = None,
        ssl_key: Optional[str] = None,
    ):
        section = RestConfig(port, host, use_https, ssl_cert, ssl_key) 
        Config._register(section)
        return Config.unique_sections[RestConfig.name]

Comment on lines +114 to +116
_inject_section(
RestConfig, "rest", default=RestConfig(), configuration_methods=[("configure_rest", RestConfig.configure_rest)]
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually do it in the __init__.py file as the checker must also be added.

#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# specific language governing permissions and limitations under the License.
# specific language governing permissions and limitations under the License.
_inject_section(
RestConfig,
"rest",
RestConfig.default_config(),
[("configure_rest", RestConfig._configure)],
add_to_unconflicted_sections=True,
)
_Checker.add_checker(_RestConfigChecker)



class RestConfig(Section):
name: str = "REST"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: str = "REST"
"""Configuration parameters for running the `Rest^` service."""
name: str = "REST"


@classmethod
def _to_dict(cls):
return {"port": cls.port, "host": cls.host, "use_https": cls.use_https, "ssl_cert": cls.ssl_cert}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If some values are None, they must not be added to the dict.

Comment on lines +48 to +51
instance.port = data.get("port", 5000)
instance.host = data.get("host", "127.0.0.1")
instance.use_https = data.get("use_https", False)
instance.ssl_cert = data.get("ssl_cert", None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot set the default values here when an attribute is not in the dictionary. We must use None instead. Otherwise, the compilation mechanism won't work when the user overrides only one attribute from a TOML file (or from an env variable)

return instance

@property
def port(self) -> int:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the getters should use the templating system to allow users to overwrite values from env variables. Use _tpl._replace_templates that should do the job.

super().__init__(config, collector)

def _check(self) -> IssueCollector:
rest_configs = cast(dict, self._config._sections.get(RestConfig.name, {}))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the RestConfig is a unique section, this line should be simpler.



def test_rest_config_default_values():
rest_config = RestConfig()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not need to do anything to retrieve the default values.
Then when the configure method is called, it should overwrite the values.
Then when loading a TOML file, this should again overwrite the values.
And the same with the env variables:

Config.rest.port = RestConfig._DEFAULT_PORT
Config.rest.host = RestConfig._DEFAULT_HOST
... # etc for all the attributes

# Now we use the configure method
Config.configure_rest(port=1)
Config.rest.port = 1
Config.rest.host = RestConfig._DEFAULT_HOST

# now we load a toml file
toml_cfg = NamedTemporaryFile(
content="""
[TAIPY]

[REST]
port = 2
host = "192.168.0.87"
        """
)
Config.load(toml_cfg.filename)
Config.rest.port = 2
Config.rest.host = "192.168.0.87"

# And finally we use env variables
assert Config.rest.port == RestConfig._DEFAULT_PORT
assert Config.rest.host== RestConfig._DEFAULT_HOST
Config.configure_rest(port="ENV[PORT]", host="ENV[HOST]")

with pytest.raises(MissingEnvVariableError):
    _ = Config.rest.port
with pytest.raises(MissingEnvVariableError):
    _ = Config.rest.host
... # etc for all the attributes

with patch.dict(os.environ, {"PORT": "3:int", "HOST": "1.2.3.4"}):
    assert Config.rest.port == 3
    assert Config.rest.bar == "1.2.3.4"

@jrobinAV jrobinAV added 🖧 Devops Operations, monitoring, maintenance, deployment, packaging ⚙️Configuration Related to Taipy Configuration Core: Rest API Related to Rest APIs 🟨 Priority: Medium Not blocking but should be addressed 📝Release Notes Impacts the Release Notes or the Documentation in general labels Jan 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚙️Configuration Related to Taipy Configuration Core: Rest API Related to Rest APIs 🖧 Devops Operations, monitoring, maintenance, deployment, packaging 🟨 Priority: Medium Not blocking but should be addressed 📝Release Notes Impacts the Release Notes or the Documentation in general
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support production environment for taipy-rest
2 participants