Skip to content

Commit

Permalink
Merge branch 'main' into support/3.2
Browse files Browse the repository at this point in the history
  • Loading branch information
dsuch committed Sep 17, 2023
2 parents 2b46b87 + fdd051c commit 18ea4b3
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 23 deletions.
60 changes: 56 additions & 4 deletions code/zato-common/src/zato/common/marshal_/api.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-

"""
Copyright (C) 2022, Zato Source s.r.o. https://zato.io
Copyright (C) 2023, Zato Source s.r.o. https://zato.io
Licensed under LGPLv3, see LICENSE.txt for terms and conditions.
"""

# stdlib
from dataclasses import asdict, _FIELDS, MISSING, _PARAMS # type: ignore
from dataclasses import asdict, _FIELDS, make_dataclass, MISSING, _PARAMS # type: ignore
from http.client import BAD_REQUEST
from inspect import isclass
from typing import Any
Expand All @@ -30,14 +30,16 @@ class _Sentinel:

# Zato
from zato.common.api import ZatoNotGiven
from zato.common.marshal_.model import BaseModel
from zato.common.typing_ import cast_, extract_from_union, is_union

# ################################################################################################################################
# ################################################################################################################################

if 0:
from dataclasses import Field
from zato.common.typing_ import any_, anydict, boolnone, dictnone, intnone, optional
from zato.common.typing_ import any_, anydict, boolnone, dictnone, intnone, optional, tuplist, type_
from zato.server.base.parallel import ParallelServer
from zato.server.service import Service

Field = Field
Expand Down Expand Up @@ -90,7 +92,7 @@ def extract_model_class(field_type:'Field') -> 'Model | None':
# ################################################################################################################################
# ################################################################################################################################

class Model:
class Model(BaseModel):
__name__: 'str'
after_created = None

Expand Down Expand Up @@ -132,6 +134,56 @@ def clone(self) -> 'any_':
out = self.__class__._zato_from_dict(None, data)
return out

@staticmethod
def build_model_from_flat_input(
server, # type: ParallelServer
sio_server_config, # type: ignore
_CySimpleIO, # type: ignore
name, # type: str
input, # type: str | tuplist
) -> 'type_[BaseModel]':

# Local imports
from zato.simpleio import is_sio_bool, is_sio_int

# Local aliases
model_fields = []

# Make sure this is a list-like container ..
if isinstance(input, str):
input = [input]

# .. build an actual SIO handler ..
_cy_simple_io = _CySimpleIO(server, sio_server_config, input) # type: ignore

# .. now, go through everything we have on input ..
for item in input:

# .. find out if this is a required element or not ..
is_optional = item.startswith('-')
is_required = not is_optional

# .. turn each element input into a Cython-based one ..
sio_elem = _cy_simple_io.convert_to_elem_instance(item, is_required) # type: ignore

# .. check if it is not a string ..
is_int:'bool' = is_sio_int(sio_elem)
is_bool:'bool' = is_sio_bool(sio_elem)

# .. turn the type into a model-compatible name ..
if is_int:
_model_type = int
elif is_bool:
_model_type = bool
else:
_model_type = str

# .. append a model-compatible definition of this field for later use ..
model_fields.append((sio_elem.name, _model_type))

model = make_dataclass(name, model_fields, bases=(Model,))
return model # type: ignore

# ################################################################################################################################
# ################################################################################################################################

Expand Down
12 changes: 12 additions & 0 deletions code/zato-common/src/zato/common/marshal_/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-

"""
Copyright (C) 2023, Zato Source s.r.o. https://zato.io
Licensed under LGPLv3, see LICENSE.txt for terms and conditions.
"""

class BaseModel:
""" This is a base class for actual models. On its own, it does not implement anything.
It is used only for type hints.
"""
6 changes: 6 additions & 0 deletions code/zato-common/src/zato/common/typing_.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
# stdlib
from dataclasses import * # type: ignore

# Zato
from zato.common.marshal_.model import BaseModel

# ################################################################################################################################
# ################################################################################################################################

Expand Down Expand Up @@ -106,6 +109,8 @@
iterator_ = iterator_
iobytes_ = io_[bytes]
listnone = anylistnone
model = type_[BaseModel]
modelnone = optional[type_[BaseModel]]
noreturn = noreturn
set_ = set_
stranydict = dict_[str, any_]
Expand Down Expand Up @@ -136,6 +141,7 @@
textio_ = textio_
textionone = textio_
tuple_ = tuple_
tuplist = union_[anylist, anytuple]
tupnone = optional[anytuple]
type_ = type_
typealias_ = typealias_
Expand Down
4 changes: 2 additions & 2 deletions code/zato-cy/src/zato/cy/simpleio.py
Original file line number Diff line number Diff line change
Expand Up @@ -1570,7 +1570,7 @@ def build(self, class_:object):
# ################################################################################################################################

@cy.returns(Elem)
def _convert_to_elem_instance(self, elem_name, is_required:cy.bint) -> Elem:
def convert_to_elem_instance(self, elem_name, is_required:cy.bint) -> Elem:

# The element we return, at this point we do not know what its exact subtype will be
_elem:Elem
Expand Down Expand Up @@ -1717,7 +1717,7 @@ def _build_io_elems(self, container, class_):
if isinstance(initial_elem, Elem):
elem = initial_elem
else:
elem = self._convert_to_elem_instance(initial_elem, is_required)
elem = self.convert_to_elem_instance(initial_elem, is_required)

# By default all Elem instances are required so we need
# to potentially overwrite it with the actual is_required value.
Expand Down
26 changes: 12 additions & 14 deletions code/zato-server/src/zato/server/connection/http_soap/outgoing.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,29 +563,27 @@ def upload(

# ################################################################################################################################

def get_by_query_id(
def rest_call(
self,
*,
cid, # type: str
id, # type: any_
model=None, # type: type_[Model] | None
callback, # type: callnone
cid, # type: str
model=None, # type: type_[Model] | None
callback, # type: callnone
params=None, # type: strdictnone
method='', # type: str
log_response=True, # type: bool
) -> 'any_':

# .. build the query parameters ..
params:'stranydict' = {
'id': id,
}

# .. invoke the system ..
# Invoke the system ..
try:
response:'Response' = self.get(cid, params)
response:'Response' = self.http_request(method, cid, params=params)
except Exception as e:
logger.warn('Caught an exception -> %s', e)
else:

# .. log what we received ..
logger.info('Get By Query ID response received -> %s', response.text)
# .. optionally, log what we received ..
if log_response:
logger.info('REST call response received -> %s', response.text)

if not response.ok:
raise Exception(response.text)
Expand Down
9 changes: 8 additions & 1 deletion code/zato-server/src/zato/server/service/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,10 +489,16 @@ def __init__(
self.environ = Bunch()
self.request = Request(self) # type: Request
self.response = Response(self.logger) # type: ignore
self.user_config = Bunch()
self.has_validate_input = False
self.has_validate_output = False

# This is where user configuration is kept
self.config = Bunch()

# This is kept for backward compatibility with code that uses self.user_config in services.
# Only self.config should be used in new services.
self.user_config = Bunch()

self.usage = 0 # How many times the service has been invoked
self.slow_threshold = maxint # After how many ms to consider the response came too late

Expand Down Expand Up @@ -1322,6 +1328,7 @@ def update(
service.wsgi_environ = wsgi_environ or {}
service.job_type = job_type
service.translate = server.kvdb.translate # type: ignore
service.config = server.user_config
service.user_config = server.user_config
service.static_config = server.static_config
service.time = server.time_util
Expand Down
57 changes: 55 additions & 2 deletions code/zato-server/src/zato/server/service/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,9 @@ def _has_io_data_class(

def set_up_class_attributes(self, class_:'type[Service]', service_store:'ServiceStore') -> 'None':

# Local aliases
_Class_SimpleIO = None # type: ignore

# Set up enforcement of what other services a given service can invoke
try:
class_.invokes
Expand Down Expand Up @@ -484,12 +487,62 @@ class _Class_SimpleIO:
has_input_data_class = self._has_io_data_class(class_, sio_input, 'Input')
has_output_data_class = self._has_io_data_class(class_, sio_output, 'Output')

# If either input or output is a dataclass but the other one is not,
# we need to turn the latter into a dataclass as well.

# We are here if output is a dataclass ..
if has_output_data_class:

# .. but input is not and it should be ..
if (not has_input_data_class) and sio_input:

# .. create a name for the dynamically-generated input model class ..
name = class_.__module__ + '_' + class_.__name__
name = name.replace('.', '_')
name += '_AutoInput'

# .. generate the input model class now ..
model_input = DataClassModel.build_model_from_flat_input(
service_store.server,
service_store.server.sio_config,
CySimpleIO,
name,
sio_input
)

# .. and assign it as input.
if _Class_SimpleIO:
_Class_SimpleIO.input = model_input # type: ignore

# We are here if input is a dataclass ..
if has_input_data_class:

# .. but output is not and it should be.
if (not has_output_data_class) and sio_output:

# .. create a name for the dynamically-generated output model class ..
name = class_.__module__ + '_' + class_.__name__
name = name.replace('.', '_')
name += '_AutoOutput'

# .. generate the input model class now ..
model_output = DataClassModel.build_model_from_flat_input(
service_store.server,
service_store.server.sio_config,
CySimpleIO,
name,
sio_output
)

if _Class_SimpleIO:
_Class_SimpleIO.output = model_output # type: ignore

if has_input_data_class or has_output_data_class:
SIOClass = DataClassSimpleIO
else:
SIOClass = CySimpleIO
SIOClass = CySimpleIO # type: ignore

_ = SIOClass.attach_sio(service_store.server, service_store.server.sio_config, class_)
_ = SIOClass.attach_sio(service_store.server, service_store.server.sio_config, class_) # type: ignore

# May be None during unit-tests - not every test provides it.
if service_store:
Expand Down

0 comments on commit 18ea4b3

Please sign in to comment.