{"action":"created","issue":{"url":"https://api.github.com/repos/docker/docker-py/issues/538","labels_url":"https://api.github.com/repos/docker/docker-py/issues/538/labels{/name}","comments_url":"https://api.github.com/repos/docker/docker-py/issues/538/comments","events_url":"https://api.github.com/repos/docker/docker-py/issues/538/events","html_url":"https://github.com/docker/docker-py/issues/538","id":65234798,"number":538,"title":"Design proposal: make the docker-py interface more 'data oriented'","user":{"login":"moysesb","id":3748237,"avatar_url":"https://avatars.githubusercontent.com/u/3748237?v=3","gravatar_id":"","url":"https://api.github.com/users/moysesb","html_url":"https://github.com/moysesb","followers_url":"https://api.github.com/users/moysesb/followers","following_url":"https://api.github.com/users/moysesb/following{/other_user}","gists_url":"https://api.github.com/users/moysesb/gists{/gist_id}","starred_url":"https://api.github.com/users/moysesb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/moysesb/subscriptions","organizations_url":"https://api.github.com/users/moysesb/orgs","repos_url":"https://api.github.com/users/moysesb/repos","events_url":"https://api.github.com/users/moysesb/events{/privacy}","received_events_url":"https://api.github.com/users/moysesb/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2015-03-30T15:13:50Z","updated_at":"2015-04-22T18:02:15Z","closed_at":null,"body":"TL;DR Make `client.build()` feel more like `client.create_container()`, and then make `create_container()`feel more like the new `build()`.\r\n\r\nIn its current form, the api exposed by docker-py is a direct one to one mapping over the names of the operations supported by the docker remote API. Since these names refer to operations that can be parametrized with query params, it would be nice if this parametrization were more explicit and dictionary-ish, so that it could be constructed independently of the call to the docker-py method that\r\nimplements the operation. \r\n\r\nFor example, say I want to send a docker context to the daemon to build. The daemon accepts a certain number of options that control both the interpretation of the contents being sent and the properties of the image that will be generated from it. These options are formally  documented in the Docker Remote API docs, so in principle it's safe to declare them as some kind of version aware data structure in docker-py.\r\n\r\nRight now, docker-py does not offer a way for a client application to declare what these options are, except implicitly in the call to `client.build()`. The build options are the named parameters for the `build()` method, which when called looks at the specific combination of arguments that was passed to decide how to construct the request to the daemon.\r\n\r\nThe proposal is to introduce a way for docker-py to construct that specific combination of parameters in a more explicit way, i.e., as a first class value that can be generated and passed around. \r\n\r\nSo, keeping the example of a 'build' operation, suppose I want to send a directory containing a Dockerfile to the daemon. The way it works now is:\r\n```python\r\n    client = Client(**params)\r\n    client.build(path='my/path', tag='some_image', \r\n    \t\trm=True, nocache=False, stream=True)\r\n```\r\nThe signature for build is\r\n```python\r\ndef build(self, path=None, tag=None, quiet=False, fileobj=None,\r\n \t\tnocache=False, rm=False, stream=False, timeout=None,\r\n\t\tcustom_context=False, encoding=None, pull=True,\r\n\t\tforcerm=False, dockerfile=None)\r\n```\r\nOne obvious problem is that here are four types of named arguments declared there: \r\n1. `path`, `fileobj` and `custom_context` control how the final context object would be built locally.\r\n2. `tag`, `quiet`, `nocache`, `rm`, `pull`, `forcerm` and `dockerfile` control how the docker daemon will execute the actual build.\r\n3. `stream` and `timeout` specify the mechanics of the interaction with the server\r\n4. `encoding` is kind of an outlier, it appears to be an implementation detail and it should be calculated automatically based on the contents of the generated context. Permissible values are `gzip`, `bzip2`, `xz`, `identity`\r\n\r\n### The proposal:\r\n\r\n   a) The arguments groups should be formally separated so that the distinction is made declaratively.\r\n   b) User code should be able to create each of these groups of parameters independently of the call to `build()`, in an \"API safe\" way.\r\n\r\n### A sketch implementation:\r\n\r\n```python\r\n  # This is the parametrization of the remote build job \r\n  BuildSpec spec = BuildSpec(api_version='1.17')\r\n  spec['tag'] = 'my_image'\r\n  spec['nocache'] = True\r\n  spec['forcerm'] = False\r\n  spec['dockerfile'] = './mydockerfile'\r\n```\r\nThe set of valid keys is specified in the Docker Remote API documentation, so there's a level of safety here. Ideally, this should not be an open dictionary, but something that docker-py knows how to construct safely, maybe via a method:\r\n```python\r\n  # the advantage here is that this function can raise an error if the\r\n  # kwargs are not valid for the current API (the one 'client' was\r\n  # constructed with)\r\n  spec = client.make_build_spec(**params)\r\n```\r\nThe other argument type is the group of parameters used to control how the context is built locally by docker-py, namely `path`, `fileobj`, and `custom_context`. A data oriented alternative could be something like:\r\n```python\r\n  # This is the parametrization of the call to docker-py's build()\r\n  Context ctx = Context(type='directory')\r\n  ctx['dockerfile'] = 'a_docker_file_name'\r\n  ctx['dockerignore'] = ['.git/', '**/*.ignorable'] \r\n  ctx['path'] = 'my/local/path'\r\n  # etc.\r\n  ```\r\n  BUT, since the docker remote API mandates that the context must be sent   as a tar file (optionally compressed), docker-py should expose a 'maker' that returns a properly constructed tar object, one that is presumably valid taking into account the api version implemented, the content supplied, etc. In the current implementation this knowledge - how to make a context - is camouflaged in the  imperative logic of the build method itself.\r\n\r\nExposing the ability to create context *values*:\r\n```python\r\n  # Expose a maker that builds a context from a local directory\r\n  Context ctx = client.make_context_from_dir(path='my/path')\r\n\r\n  # Here's another way\r\n  Context ctx = client.make_context_from_dockerfile('path/to/dockerfile')\r\n\r\n  # And another\r\n  Context ctx = client.make_context_from_tarball('path/to/tarball')\r\n```\r\n\r\nAfter the user has obtained a context value, it can optionally tweak it some more before passing it along (for example, merging the dockerignore list from the context with another list, adding some\r\nfiles to the context data object, etc)\r\n\r\nAnd then the implementation of build() would look like:\r\n```python\r\ndef client.build(context, build_spec, stream=True, timeout=60):\r\n    #validate the context and the build_spec\r\n    \r\n    #then\r\n    response = self._post(\r\n                       u,\r\n                       data=context['data'], # put there by make_context_from_*\r\n                       params=build_spec,    # might need a transformation here\r\n                       headers=headers,\r\n                       stream=stream,\r\n                       timeout=timeout,\r\n    )\r\n\r\n    # now use the response as directed by the stream and timeout arguments\r\n```\r\n\r\nGood, bad, ugly?\r\n"},"comment":{"url":"https://api.github.com/repos/docker/docker-py/issues/comments/95285856","html_url":"https://github.com/docker/docker-py/issues/538#issuecomment-95285856","issue_url":"https://api.github.com/repos/docker/docker-py/issues/538","id":95285856,"user":{"login":"shin-","id":1086876,"avatar_url":"https://avatars.githubusercontent.com/u/1086876?v=3","gravatar_id":"","url":"https://api.github.com/users/shin-","html_url":"https://github.com/shin-","followers_url":"https://api.github.com/users/shin-/followers","following_url":"https://api.github.com/users/shin-/following{/other_user}","gists_url":"https://api.github.com/users/shin-/gists{/gist_id}","starred_url":"https://api.github.com/users/shin-/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shin-/subscriptions","organizations_url":"https://api.github.com/users/shin-/orgs","repos_url":"https://api.github.com/users/shin-/repos","events_url":"https://api.github.com/users/shin-/events{/privacy}","received_events_url":"https://api.github.com/users/shin-/received_events","type":"User","site_admin":false},"created_at":"2015-04-22T18:02:15Z","updated_at":"2015-04-22T18:02:15Z","body":"Sorry for the absence of response, haven't had much time to spend on the project recently. Things could definitely be better in that regard, but there is a lot to consider with regards to backwards compatibility, ease of use and familiarity. I'll give this proposal another look later, and answer more thoroughly."}}