We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
_check_api_features should support pure dict params
https://github.com/docker/docker-py/blob/2.4.2-release/docker/api/service.py#L41

Originally reported: https://github.com/moby/moby/issues/34116#issuecomment-315484688

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `docker/api/service.py`
Content:
```
1 import warnings
2 from .. import auth, errors, utils
3 from ..types import ServiceMode
4 
5 
6 def _check_api_features(version, task_template, update_config):
7     if update_config is not None:
8         if utils.version_lt(version, '1.25'):
9             if 'MaxFailureRatio' in update_config:
10                 raise errors.InvalidVersion(
11                     'UpdateConfig.max_failure_ratio is not supported in'
12                     ' API version < 1.25'
13                 )
14             if 'Monitor' in update_config:
15                 raise errors.InvalidVersion(
16                     'UpdateConfig.monitor is not supported in'
17                     ' API version < 1.25'
18                 )
19 
20     if task_template is not None:
21         if 'ForceUpdate' in task_template and utils.version_lt(
22                 version, '1.25'):
23             raise errors.InvalidVersion(
24                 'force_update is not supported in API version < 1.25'
25             )
26 
27         if task_template.get('Placement'):
28             if utils.version_lt(version, '1.30'):
29                 if task_template['Placement'].get('Platforms'):
30                     raise errors.InvalidVersion(
31                         'Placement.platforms is not supported in'
32                         ' API version < 1.30'
33                     )
34 
35             if utils.version_lt(version, '1.27'):
36                 if task_template['Placement'].get('Preferences'):
37                     raise errors.InvalidVersion(
38                         'Placement.preferences is not supported in'
39                         ' API version < 1.27'
40                     )
41         if task_template.container_spec.get('TTY'):
42             if utils.version_lt(version, '1.25'):
43                 raise errors.InvalidVersion(
44                     'ContainerSpec.TTY is not supported in API version < 1.25'
45                 )
46 
47 
48 class ServiceApiMixin(object):
49     @utils.minimum_version('1.24')
50     def create_service(
51             self, task_template, name=None, labels=None, mode=None,
52             update_config=None, networks=None, endpoint_config=None,
53             endpoint_spec=None
54     ):
55         """
56         Create a service.
57 
58         Args:
59             task_template (TaskTemplate): Specification of the task to start as
60                 part of the new service.
61             name (string): User-defined name for the service. Optional.
62             labels (dict): A map of labels to associate with the service.
63                 Optional.
64             mode (ServiceMode): Scheduling mode for the service (replicated
65                 or global). Defaults to replicated.
66             update_config (UpdateConfig): Specification for the update strategy
67                 of the service. Default: ``None``
68             networks (:py:class:`list`): List of network names or IDs to attach
69                 the service to. Default: ``None``.
70             endpoint_spec (EndpointSpec): Properties that can be configured to
71                 access and load balance a service. Default: ``None``.
72 
73         Returns:
74             A dictionary containing an ``ID`` key for the newly created
75             service.
76 
77         Raises:
78             :py:class:`docker.errors.APIError`
79                 If the server returns an error.
80         """
81         if endpoint_config is not None:
82             warnings.warn(
83                 'endpoint_config has been renamed to endpoint_spec.',
84                 DeprecationWarning
85             )
86             endpoint_spec = endpoint_config
87 
88         _check_api_features(self._version, task_template, update_config)
89 
90         url = self._url('/services/create')
91         headers = {}
92         image = task_template.get('ContainerSpec', {}).get('Image', None)
93         if image is None:
94             raise errors.DockerException(
95                 'Missing mandatory Image key in ContainerSpec'
96             )
97         if mode and not isinstance(mode, dict):
98             mode = ServiceMode(mode)
99 
100         registry, repo_name = auth.resolve_repository_name(image)
101         auth_header = auth.get_config_header(self, registry)
102         if auth_header:
103             headers['X-Registry-Auth'] = auth_header
104         data = {
105             'Name': name,
106             'Labels': labels,
107             'TaskTemplate': task_template,
108             'Mode': mode,
109             'Networks': utils.convert_service_networks(networks),
110             'EndpointSpec': endpoint_spec
111         }
112 
113         if update_config is not None:
114             data['UpdateConfig'] = update_config
115 
116         return self._result(
117             self._post_json(url, data=data, headers=headers), True
118         )
119 
120     @utils.minimum_version('1.24')
121     @utils.check_resource('service')
122     def inspect_service(self, service):
123         """
124         Return information about a service.
125 
126         Args:
127             service (str): Service name or ID
128 
129         Returns:
130             ``True`` if successful.
131 
132         Raises:
133             :py:class:`docker.errors.APIError`
134                 If the server returns an error.
135         """
136         url = self._url('/services/{0}', service)
137         return self._result(self._get(url), True)
138 
139     @utils.minimum_version('1.24')
140     @utils.check_resource('task')
141     def inspect_task(self, task):
142         """
143         Retrieve information about a task.
144 
145         Args:
146             task (str): Task ID
147 
148         Returns:
149             (dict): Information about the task.
150 
151         Raises:
152             :py:class:`docker.errors.APIError`
153                 If the server returns an error.
154         """
155         url = self._url('/tasks/{0}', task)
156         return self._result(self._get(url), True)
157 
158     @utils.minimum_version('1.24')
159     @utils.check_resource('service')
160     def remove_service(self, service):
161         """
162         Stop and remove a service.
163 
164         Args:
165             service (str): Service name or ID
166 
167         Returns:
168             ``True`` if successful.
169 
170         Raises:
171             :py:class:`docker.errors.APIError`
172                 If the server returns an error.
173         """
174 
175         url = self._url('/services/{0}', service)
176         resp = self._delete(url)
177         self._raise_for_status(resp)
178         return True
179 
180     @utils.minimum_version('1.24')
181     def services(self, filters=None):
182         """
183         List services.
184 
185         Args:
186             filters (dict): Filters to process on the nodes list. Valid
187                 filters: ``id`` and ``name``. Default: ``None``.
188 
189         Returns:
190             A list of dictionaries containing data about each service.
191 
192         Raises:
193             :py:class:`docker.errors.APIError`
194                 If the server returns an error.
195         """
196         params = {
197             'filters': utils.convert_filters(filters) if filters else None
198         }
199         url = self._url('/services')
200         return self._result(self._get(url, params=params), True)
201 
202     @utils.minimum_version('1.25')
203     @utils.check_resource('service')
204     def service_logs(self, service, details=False, follow=False, stdout=False,
205                      stderr=False, since=0, timestamps=False, tail='all',
206                      is_tty=None):
207         """
208             Get log stream for a service.
209             Note: This endpoint works only for services with the ``json-file``
210             or ``journald`` logging drivers.
211 
212             Args:
213                 service (str): ID or name of the service
214                 details (bool): Show extra details provided to logs.
215                     Default: ``False``
216                 follow (bool): Keep connection open to read logs as they are
217                     sent by the Engine. Default: ``False``
218                 stdout (bool): Return logs from ``stdout``. Default: ``False``
219                 stderr (bool): Return logs from ``stderr``. Default: ``False``
220                 since (int): UNIX timestamp for the logs staring point.
221                     Default: 0
222                 timestamps (bool): Add timestamps to every log line.
223                 tail (string or int): Number of log lines to be returned,
224                     counting from the current end of the logs. Specify an
225                     integer or ``'all'`` to output all log lines.
226                     Default: ``all``
227                 is_tty (bool): Whether the service's :py:class:`ContainerSpec`
228                     enables the TTY option. If omitted, the method will query
229                     the Engine for the information, causing an additional
230                     roundtrip.
231 
232             Returns (generator): Logs for the service.
233         """
234         params = {
235             'details': details,
236             'follow': follow,
237             'stdout': stdout,
238             'stderr': stderr,
239             'since': since,
240             'timestamps': timestamps,
241             'tail': tail
242         }
243 
244         url = self._url('/services/{0}/logs', service)
245         res = self._get(url, params=params, stream=True)
246         if is_tty is None:
247             is_tty = self.inspect_service(
248                 service
249             )['Spec']['TaskTemplate']['ContainerSpec'].get('TTY', False)
250         return self._get_result_tty(True, res, is_tty)
251 
252     @utils.minimum_version('1.24')
253     def tasks(self, filters=None):
254         """
255         Retrieve a list of tasks.
256 
257         Args:
258             filters (dict): A map of filters to process on the tasks list.
259                 Valid filters: ``id``, ``name``, ``service``, ``node``,
260                 ``label`` and ``desired-state``.
261 
262         Returns:
263             (:py:class:`list`): List of task dictionaries.
264 
265         Raises:
266             :py:class:`docker.errors.APIError`
267                 If the server returns an error.
268         """
269 
270         params = {
271             'filters': utils.convert_filters(filters) if filters else None
272         }
273         url = self._url('/tasks')
274         return self._result(self._get(url, params=params), True)
275 
276     @utils.minimum_version('1.24')
277     @utils.check_resource('service')
278     def update_service(self, service, version, task_template=None, name=None,
279                        labels=None, mode=None, update_config=None,
280                        networks=None, endpoint_config=None,
281                        endpoint_spec=None):
282         """
283         Update a service.
284 
285         Args:
286             service (string): A service identifier (either its name or service
287                 ID).
288             version (int): The version number of the service object being
289                 updated. This is required to avoid conflicting writes.
290             task_template (TaskTemplate): Specification of the updated task to
291                 start as part of the service.
292             name (string): New name for the service. Optional.
293             labels (dict): A map of labels to associate with the service.
294                 Optional.
295             mode (ServiceMode): Scheduling mode for the service (replicated
296                 or global). Defaults to replicated.
297             update_config (UpdateConfig): Specification for the update strategy
298                 of the service. Default: ``None``.
299             networks (:py:class:`list`): List of network names or IDs to attach
300                 the service to. Default: ``None``.
301             endpoint_spec (EndpointSpec): Properties that can be configured to
302                 access and load balance a service. Default: ``None``.
303 
304         Returns:
305             ``True`` if successful.
306 
307         Raises:
308             :py:class:`docker.errors.APIError`
309                 If the server returns an error.
310         """
311         if endpoint_config is not None:
312             warnings.warn(
313                 'endpoint_config has been renamed to endpoint_spec.',
314                 DeprecationWarning
315             )
316             endpoint_spec = endpoint_config
317 
318         _check_api_features(self._version, task_template, update_config)
319 
320         url = self._url('/services/{0}/update', service)
321         data = {}
322         headers = {}
323         if name is not None:
324             data['Name'] = name
325         if labels is not None:
326             data['Labels'] = labels
327         if mode is not None:
328             if not isinstance(mode, dict):
329                 mode = ServiceMode(mode)
330             data['Mode'] = mode
331         if task_template is not None:
332             image = task_template.get('ContainerSpec', {}).get('Image', None)
333             if image is not None:
334                 registry, repo_name = auth.resolve_repository_name(image)
335                 auth_header = auth.get_config_header(self, registry)
336                 if auth_header:
337                     headers['X-Registry-Auth'] = auth_header
338             data['TaskTemplate'] = task_template
339         if update_config is not None:
340             data['UpdateConfig'] = update_config
341 
342         if networks is not None:
343             data['Networks'] = utils.convert_service_networks(networks)
344         if endpoint_spec is not None:
345             data['EndpointSpec'] = endpoint_spec
346 
347         resp = self._post_json(
348             url, data=data, params={'version': version}, headers=headers
349         )
350         self._raise_for_status(resp)
351         return True
352
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/docker/api/service.py b/docker/api/service.py
--- a/docker/api/service.py
+++ b/docker/api/service.py
@@ -38,7 +38,7 @@
                         'Placement.preferences is not supported in'
                         ' API version < 1.27'
                     )
-        if task_template.container_spec.get('TTY'):
+        if task_template.get('ContainerSpec', {}).get('TTY'):
             if utils.version_lt(version, '1.25'):
                 raise errors.InvalidVersion(
                     'ContainerSpec.TTY is not supported in API version < 1.25'

{"golden_diff": "diff --git a/docker/api/service.py b/docker/api/service.py\n--- a/docker/api/service.py\n+++ b/docker/api/service.py\n@@ -38,7 +38,7 @@\n                         'Placement.preferences is not supported in'\n                         ' API version < 1.27'\n                     )\n-        if task_template.container_spec.get('TTY'):\n+        if task_template.get('ContainerSpec', {}).get('TTY'):\n             if utils.version_lt(version, '1.25'):\n                 raise errors.InvalidVersion(\n                     'ContainerSpec.TTY is not supported in API version < 1.25'\n", "issue": "_check_api_features should support pure dict params\nhttps://github.com/docker/docker-py/blob/2.4.2-release/docker/api/service.py#L41\r\n\r\nOriginally reported: https://github.com/moby/moby/issues/34116#issuecomment-315484688\n", "before_files": [{"content": "import warnings\nfrom .. import auth, errors, utils\nfrom ..types import ServiceMode\n\n\ndef _check_api_features(version, task_template, update_config):\n    if update_config is not None:\n        if utils.version_lt(version, '1.25'):\n            if 'MaxFailureRatio' in update_config:\n                raise errors.InvalidVersion(\n                    'UpdateConfig.max_failure_ratio is not supported in'\n                    ' API version < 1.25'\n                )\n            if 'Monitor' in update_config:\n                raise errors.InvalidVersion(\n                    'UpdateConfig.monitor is not supported in'\n                    ' API version < 1.25'\n                )\n\n    if task_template is not None:\n        if 'ForceUpdate' in task_template and utils.version_lt(\n                version, '1.25'):\n            raise errors.InvalidVersion(\n                'force_update is not supported in API version < 1.25'\n            )\n\n        if task_template.get('Placement'):\n            if utils.version_lt(version, '1.30'):\n                if task_template['Placement'].get('Platforms'):\n                    raise errors.InvalidVersion(\n                        'Placement.platforms is not supported in'\n                        ' API version < 1.30'\n                    )\n\n            if utils.version_lt(version, '1.27'):\n                if task_template['Placement'].get('Preferences'):\n                    raise errors.InvalidVersion(\n                        'Placement.preferences is not supported in'\n                        ' API version < 1.27'\n                    )\n        if task_template.container_spec.get('TTY'):\n            if utils.version_lt(version, '1.25'):\n                raise errors.InvalidVersion(\n                    'ContainerSpec.TTY is not supported in API version < 1.25'\n                )\n\n\nclass ServiceApiMixin(object):\n    @utils.minimum_version('1.24')\n    def create_service(\n            self, task_template, name=None, labels=None, mode=None,\n            update_config=None, networks=None, endpoint_config=None,\n            endpoint_spec=None\n    ):\n        \"\"\"\n        Create a service.\n\n        Args:\n            task_template (TaskTemplate): Specification of the task to start as\n                part of the new service.\n            name (string): User-defined name for the service. Optional.\n            labels (dict): A map of labels to associate with the service.\n                Optional.\n            mode (ServiceMode): Scheduling mode for the service (replicated\n                or global). Defaults to replicated.\n            update_config (UpdateConfig): Specification for the update strategy\n                of the service. Default: ``None``\n            networks (:py:class:`list`): List of network names or IDs to attach\n                the service to. Default: ``None``.\n            endpoint_spec (EndpointSpec): Properties that can be configured to\n                access and load balance a service. Default: ``None``.\n\n        Returns:\n            A dictionary containing an ``ID`` key for the newly created\n            service.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        if endpoint_config is not None:\n            warnings.warn(\n                'endpoint_config has been renamed to endpoint_spec.',\n                DeprecationWarning\n            )\n            endpoint_spec = endpoint_config\n\n        _check_api_features(self._version, task_template, update_config)\n\n        url = self._url('/services/create')\n        headers = {}\n        image = task_template.get('ContainerSpec', {}).get('Image', None)\n        if image is None:\n            raise errors.DockerException(\n                'Missing mandatory Image key in ContainerSpec'\n            )\n        if mode and not isinstance(mode, dict):\n            mode = ServiceMode(mode)\n\n        registry, repo_name = auth.resolve_repository_name(image)\n        auth_header = auth.get_config_header(self, registry)\n        if auth_header:\n            headers['X-Registry-Auth'] = auth_header\n        data = {\n            'Name': name,\n            'Labels': labels,\n            'TaskTemplate': task_template,\n            'Mode': mode,\n            'Networks': utils.convert_service_networks(networks),\n            'EndpointSpec': endpoint_spec\n        }\n\n        if update_config is not None:\n            data['UpdateConfig'] = update_config\n\n        return self._result(\n            self._post_json(url, data=data, headers=headers), True\n        )\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('service')\n    def inspect_service(self, service):\n        \"\"\"\n        Return information about a service.\n\n        Args:\n            service (str): Service name or ID\n\n        Returns:\n            ``True`` if successful.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        url = self._url('/services/{0}', service)\n        return self._result(self._get(url), True)\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('task')\n    def inspect_task(self, task):\n        \"\"\"\n        Retrieve information about a task.\n\n        Args:\n            task (str): Task ID\n\n        Returns:\n            (dict): Information about the task.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        url = self._url('/tasks/{0}', task)\n        return self._result(self._get(url), True)\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('service')\n    def remove_service(self, service):\n        \"\"\"\n        Stop and remove a service.\n\n        Args:\n            service (str): Service name or ID\n\n        Returns:\n            ``True`` if successful.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n\n        url = self._url('/services/{0}', service)\n        resp = self._delete(url)\n        self._raise_for_status(resp)\n        return True\n\n    @utils.minimum_version('1.24')\n    def services(self, filters=None):\n        \"\"\"\n        List services.\n\n        Args:\n            filters (dict): Filters to process on the nodes list. Valid\n                filters: ``id`` and ``name``. Default: ``None``.\n\n        Returns:\n            A list of dictionaries containing data about each service.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        params = {\n            'filters': utils.convert_filters(filters) if filters else None\n        }\n        url = self._url('/services')\n        return self._result(self._get(url, params=params), True)\n\n    @utils.minimum_version('1.25')\n    @utils.check_resource('service')\n    def service_logs(self, service, details=False, follow=False, stdout=False,\n                     stderr=False, since=0, timestamps=False, tail='all',\n                     is_tty=None):\n        \"\"\"\n            Get log stream for a service.\n            Note: This endpoint works only for services with the ``json-file``\n            or ``journald`` logging drivers.\n\n            Args:\n                service (str): ID or name of the service\n                details (bool): Show extra details provided to logs.\n                    Default: ``False``\n                follow (bool): Keep connection open to read logs as they are\n                    sent by the Engine. Default: ``False``\n                stdout (bool): Return logs from ``stdout``. Default: ``False``\n                stderr (bool): Return logs from ``stderr``. Default: ``False``\n                since (int): UNIX timestamp for the logs staring point.\n                    Default: 0\n                timestamps (bool): Add timestamps to every log line.\n                tail (string or int): Number of log lines to be returned,\n                    counting from the current end of the logs. Specify an\n                    integer or ``'all'`` to output all log lines.\n                    Default: ``all``\n                is_tty (bool): Whether the service's :py:class:`ContainerSpec`\n                    enables the TTY option. If omitted, the method will query\n                    the Engine for the information, causing an additional\n                    roundtrip.\n\n            Returns (generator): Logs for the service.\n        \"\"\"\n        params = {\n            'details': details,\n            'follow': follow,\n            'stdout': stdout,\n            'stderr': stderr,\n            'since': since,\n            'timestamps': timestamps,\n            'tail': tail\n        }\n\n        url = self._url('/services/{0}/logs', service)\n        res = self._get(url, params=params, stream=True)\n        if is_tty is None:\n            is_tty = self.inspect_service(\n                service\n            )['Spec']['TaskTemplate']['ContainerSpec'].get('TTY', False)\n        return self._get_result_tty(True, res, is_tty)\n\n    @utils.minimum_version('1.24')\n    def tasks(self, filters=None):\n        \"\"\"\n        Retrieve a list of tasks.\n\n        Args:\n            filters (dict): A map of filters to process on the tasks list.\n                Valid filters: ``id``, ``name``, ``service``, ``node``,\n                ``label`` and ``desired-state``.\n\n        Returns:\n            (:py:class:`list`): List of task dictionaries.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n\n        params = {\n            'filters': utils.convert_filters(filters) if filters else None\n        }\n        url = self._url('/tasks')\n        return self._result(self._get(url, params=params), True)\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('service')\n    def update_service(self, service, version, task_template=None, name=None,\n                       labels=None, mode=None, update_config=None,\n                       networks=None, endpoint_config=None,\n                       endpoint_spec=None):\n        \"\"\"\n        Update a service.\n\n        Args:\n            service (string): A service identifier (either its name or service\n                ID).\n            version (int): The version number of the service object being\n                updated. This is required to avoid conflicting writes.\n            task_template (TaskTemplate): Specification of the updated task to\n                start as part of the service.\n            name (string): New name for the service. Optional.\n            labels (dict): A map of labels to associate with the service.\n                Optional.\n            mode (ServiceMode): Scheduling mode for the service (replicated\n                or global). Defaults to replicated.\n            update_config (UpdateConfig): Specification for the update strategy\n                of the service. Default: ``None``.\n            networks (:py:class:`list`): List of network names or IDs to attach\n                the service to. Default: ``None``.\n            endpoint_spec (EndpointSpec): Properties that can be configured to\n                access and load balance a service. Default: ``None``.\n\n        Returns:\n            ``True`` if successful.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        if endpoint_config is not None:\n            warnings.warn(\n                'endpoint_config has been renamed to endpoint_spec.',\n                DeprecationWarning\n            )\n            endpoint_spec = endpoint_config\n\n        _check_api_features(self._version, task_template, update_config)\n\n        url = self._url('/services/{0}/update', service)\n        data = {}\n        headers = {}\n        if name is not None:\n            data['Name'] = name\n        if labels is not None:\n            data['Labels'] = labels\n        if mode is not None:\n            if not isinstance(mode, dict):\n                mode = ServiceMode(mode)\n            data['Mode'] = mode\n        if task_template is not None:\n            image = task_template.get('ContainerSpec', {}).get('Image', None)\n            if image is not None:\n                registry, repo_name = auth.resolve_repository_name(image)\n                auth_header = auth.get_config_header(self, registry)\n                if auth_header:\n                    headers['X-Registry-Auth'] = auth_header\n            data['TaskTemplate'] = task_template\n        if update_config is not None:\n            data['UpdateConfig'] = update_config\n\n        if networks is not None:\n            data['Networks'] = utils.convert_service_networks(networks)\n        if endpoint_spec is not None:\n            data['EndpointSpec'] = endpoint_spec\n\n        resp = self._post_json(\n            url, data=data, params={'version': version}, headers=headers\n        )\n        self._raise_for_status(resp)\n        return True\n", "path": "docker/api/service.py"}], "after_files": [{"content": "import warnings\nfrom .. import auth, errors, utils\nfrom ..types import ServiceMode\n\n\ndef _check_api_features(version, task_template, update_config):\n    if update_config is not None:\n        if utils.version_lt(version, '1.25'):\n            if 'MaxFailureRatio' in update_config:\n                raise errors.InvalidVersion(\n                    'UpdateConfig.max_failure_ratio is not supported in'\n                    ' API version < 1.25'\n                )\n            if 'Monitor' in update_config:\n                raise errors.InvalidVersion(\n                    'UpdateConfig.monitor is not supported in'\n                    ' API version < 1.25'\n                )\n\n    if task_template is not None:\n        if 'ForceUpdate' in task_template and utils.version_lt(\n                version, '1.25'):\n            raise errors.InvalidVersion(\n                'force_update is not supported in API version < 1.25'\n            )\n\n        if task_template.get('Placement'):\n            if utils.version_lt(version, '1.30'):\n                if task_template['Placement'].get('Platforms'):\n                    raise errors.InvalidVersion(\n                        'Placement.platforms is not supported in'\n                        ' API version < 1.30'\n                    )\n\n            if utils.version_lt(version, '1.27'):\n                if task_template['Placement'].get('Preferences'):\n                    raise errors.InvalidVersion(\n                        'Placement.preferences is not supported in'\n                        ' API version < 1.27'\n                    )\n        if task_template.get('ContainerSpec', {}).get('TTY'):\n            if utils.version_lt(version, '1.25'):\n                raise errors.InvalidVersion(\n                    'ContainerSpec.TTY is not supported in API version < 1.25'\n                )\n\n\nclass ServiceApiMixin(object):\n    @utils.minimum_version('1.24')\n    def create_service(\n            self, task_template, name=None, labels=None, mode=None,\n            update_config=None, networks=None, endpoint_config=None,\n            endpoint_spec=None\n    ):\n        \"\"\"\n        Create a service.\n\n        Args:\n            task_template (TaskTemplate): Specification of the task to start as\n                part of the new service.\n            name (string): User-defined name for the service. Optional.\n            labels (dict): A map of labels to associate with the service.\n                Optional.\n            mode (ServiceMode): Scheduling mode for the service (replicated\n                or global). Defaults to replicated.\n            update_config (UpdateConfig): Specification for the update strategy\n                of the service. Default: ``None``\n            networks (:py:class:`list`): List of network names or IDs to attach\n                the service to. Default: ``None``.\n            endpoint_spec (EndpointSpec): Properties that can be configured to\n                access and load balance a service. Default: ``None``.\n\n        Returns:\n            A dictionary containing an ``ID`` key for the newly created\n            service.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        if endpoint_config is not None:\n            warnings.warn(\n                'endpoint_config has been renamed to endpoint_spec.',\n                DeprecationWarning\n            )\n            endpoint_spec = endpoint_config\n\n        _check_api_features(self._version, task_template, update_config)\n\n        url = self._url('/services/create')\n        headers = {}\n        image = task_template.get('ContainerSpec', {}).get('Image', None)\n        if image is None:\n            raise errors.DockerException(\n                'Missing mandatory Image key in ContainerSpec'\n            )\n        if mode and not isinstance(mode, dict):\n            mode = ServiceMode(mode)\n\n        registry, repo_name = auth.resolve_repository_name(image)\n        auth_header = auth.get_config_header(self, registry)\n        if auth_header:\n            headers['X-Registry-Auth'] = auth_header\n        data = {\n            'Name': name,\n            'Labels': labels,\n            'TaskTemplate': task_template,\n            'Mode': mode,\n            'Networks': utils.convert_service_networks(networks),\n            'EndpointSpec': endpoint_spec\n        }\n\n        if update_config is not None:\n            data['UpdateConfig'] = update_config\n\n        return self._result(\n            self._post_json(url, data=data, headers=headers), True\n        )\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('service')\n    def inspect_service(self, service):\n        \"\"\"\n        Return information about a service.\n\n        Args:\n            service (str): Service name or ID\n\n        Returns:\n            ``True`` if successful.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        url = self._url('/services/{0}', service)\n        return self._result(self._get(url), True)\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('task')\n    def inspect_task(self, task):\n        \"\"\"\n        Retrieve information about a task.\n\n        Args:\n            task (str): Task ID\n\n        Returns:\n            (dict): Information about the task.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        url = self._url('/tasks/{0}', task)\n        return self._result(self._get(url), True)\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('service')\n    def remove_service(self, service):\n        \"\"\"\n        Stop and remove a service.\n\n        Args:\n            service (str): Service name or ID\n\n        Returns:\n            ``True`` if successful.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n\n        url = self._url('/services/{0}', service)\n        resp = self._delete(url)\n        self._raise_for_status(resp)\n        return True\n\n    @utils.minimum_version('1.24')\n    def services(self, filters=None):\n        \"\"\"\n        List services.\n\n        Args:\n            filters (dict): Filters to process on the nodes list. Valid\n                filters: ``id`` and ``name``. Default: ``None``.\n\n        Returns:\n            A list of dictionaries containing data about each service.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        params = {\n            'filters': utils.convert_filters(filters) if filters else None\n        }\n        url = self._url('/services')\n        return self._result(self._get(url, params=params), True)\n\n    @utils.minimum_version('1.25')\n    @utils.check_resource('service')\n    def service_logs(self, service, details=False, follow=False, stdout=False,\n                     stderr=False, since=0, timestamps=False, tail='all',\n                     is_tty=None):\n        \"\"\"\n            Get log stream for a service.\n            Note: This endpoint works only for services with the ``json-file``\n            or ``journald`` logging drivers.\n\n            Args:\n                service (str): ID or name of the service\n                details (bool): Show extra details provided to logs.\n                    Default: ``False``\n                follow (bool): Keep connection open to read logs as they are\n                    sent by the Engine. Default: ``False``\n                stdout (bool): Return logs from ``stdout``. Default: ``False``\n                stderr (bool): Return logs from ``stderr``. Default: ``False``\n                since (int): UNIX timestamp for the logs staring point.\n                    Default: 0\n                timestamps (bool): Add timestamps to every log line.\n                tail (string or int): Number of log lines to be returned,\n                    counting from the current end of the logs. Specify an\n                    integer or ``'all'`` to output all log lines.\n                    Default: ``all``\n                is_tty (bool): Whether the service's :py:class:`ContainerSpec`\n                    enables the TTY option. If omitted, the method will query\n                    the Engine for the information, causing an additional\n                    roundtrip.\n\n            Returns (generator): Logs for the service.\n        \"\"\"\n        params = {\n            'details': details,\n            'follow': follow,\n            'stdout': stdout,\n            'stderr': stderr,\n            'since': since,\n            'timestamps': timestamps,\n            'tail': tail\n        }\n\n        url = self._url('/services/{0}/logs', service)\n        res = self._get(url, params=params, stream=True)\n        if is_tty is None:\n            is_tty = self.inspect_service(\n                service\n            )['Spec']['TaskTemplate']['ContainerSpec'].get('TTY', False)\n        return self._get_result_tty(True, res, is_tty)\n\n    @utils.minimum_version('1.24')\n    def tasks(self, filters=None):\n        \"\"\"\n        Retrieve a list of tasks.\n\n        Args:\n            filters (dict): A map of filters to process on the tasks list.\n                Valid filters: ``id``, ``name``, ``service``, ``node``,\n                ``label`` and ``desired-state``.\n\n        Returns:\n            (:py:class:`list`): List of task dictionaries.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n\n        params = {\n            'filters': utils.convert_filters(filters) if filters else None\n        }\n        url = self._url('/tasks')\n        return self._result(self._get(url, params=params), True)\n\n    @utils.minimum_version('1.24')\n    @utils.check_resource('service')\n    def update_service(self, service, version, task_template=None, name=None,\n                       labels=None, mode=None, update_config=None,\n                       networks=None, endpoint_config=None,\n                       endpoint_spec=None):\n        \"\"\"\n        Update a service.\n\n        Args:\n            service (string): A service identifier (either its name or service\n                ID).\n            version (int): The version number of the service object being\n                updated. This is required to avoid conflicting writes.\n            task_template (TaskTemplate): Specification of the updated task to\n                start as part of the service.\n            name (string): New name for the service. Optional.\n            labels (dict): A map of labels to associate with the service.\n                Optional.\n            mode (ServiceMode): Scheduling mode for the service (replicated\n                or global). Defaults to replicated.\n            update_config (UpdateConfig): Specification for the update strategy\n                of the service. Default: ``None``.\n            networks (:py:class:`list`): List of network names or IDs to attach\n                the service to. Default: ``None``.\n            endpoint_spec (EndpointSpec): Properties that can be configured to\n                access and load balance a service. Default: ``None``.\n\n        Returns:\n            ``True`` if successful.\n\n        Raises:\n            :py:class:`docker.errors.APIError`\n                If the server returns an error.\n        \"\"\"\n        if endpoint_config is not None:\n            warnings.warn(\n                'endpoint_config has been renamed to endpoint_spec.',\n                DeprecationWarning\n            )\n            endpoint_spec = endpoint_config\n\n        _check_api_features(self._version, task_template, update_config)\n\n        url = self._url('/services/{0}/update', service)\n        data = {}\n        headers = {}\n        if name is not None:\n            data['Name'] = name\n        if labels is not None:\n            data['Labels'] = labels\n        if mode is not None:\n            if not isinstance(mode, dict):\n                mode = ServiceMode(mode)\n            data['Mode'] = mode\n        if task_template is not None:\n            image = task_template.get('ContainerSpec', {}).get('Image', None)\n            if image is not None:\n                registry, repo_name = auth.resolve_repository_name(image)\n                auth_header = auth.get_config_header(self, registry)\n                if auth_header:\n                    headers['X-Registry-Auth'] = auth_header\n            data['TaskTemplate'] = task_template\n        if update_config is not None:\n            data['UpdateConfig'] = update_config\n\n        if networks is not None:\n            data['Networks'] = utils.convert_service_networks(networks)\n        if endpoint_spec is not None:\n            data['EndpointSpec'] = endpoint_spec\n\n        resp = self._post_json(\n            url, data=data, params={'version': version}, headers=headers\n        )\n        self._raise_for_status(resp)\n        return True\n", "path": "docker/api/service.py"}]}