Repository navigation
Explore going the route of using Clients ? #861
Copy link
Copy link
Closed
Labels
api: coreauthtype: questionRequest for information or clarification. Not an issue.Request for information or clarification. Not an issue.
Description
After our last talk, there have been quite a few different ideas tossed around to make it clear and obvious which credentials and project IDs are in use during a particular API call, some of those have been....
- Changing the default values globally
- Making "Connections" a context manager (
with connection: # do something) - Creating the concept of a client
We do (1), are talking about doing (2), while the others tend to do (3) -- and comparing the code, I think (3) is the nicest.
gcloud-node:
var gcloud = require('gcloud');
var project1_storage = gcloud.storage({projectId: 'project1', keyFilename: '/path/to/key'});
var project2_storage_auto = gcloud.storage();
# Do things with the two "clients"...
bucket1 = project1_storage.get_bucket('bucket-1')
bucket2 = project2_storage_auto.get_bucket('bucket2')gcloud-ruby
require 'gcloud/storage'
project1_storage = Gcloud.storage "project-id-1" "/path/to/key"
project2_storage_auto = Gcloud.storage # Magically figure out the project ID and credentials
# Do things with the two "clients"...
bucket1 = project1_storage.find_bucket "bucket-1"
bucket2 = project2_storage_auto.find_bucket "bucket-2"gcloud-python
from gcloud import storage
from gcloud.credentials import get_for_service_account_json
# Create two different credentials.
credentials1 = get_for_service_account_json('key1.json')
credentials2 = get_for_service_account_json('key2.json')
# Create two different connections.
connection1 = storage.Connection(credentials=credentials1)
connection2 = storage.Connection(credentials=credentials2)
# Get two different buckets
bucket1 = storage.get_bucket('bucket-1', project='project1', connection=connection1)
bucket2 = storage.get_bucket('bucket-2', project='project2', connection=connection2)gcloud-python if we followed the client pattern:
from gcloud import storage
project1_storage = storage.Client('project1', '/path/to/key')
project2_storage_auto = storage.Client()
# Do things with the two "clients"...
bucket1 = project1_storage.get_bucket('bucket-1')
bucket2 = project2_storage_auto.get_bucket('bucket-2')Another option for gcloud-python using the client pattern:
import gcloud
project1_storage = gcloud.storage('project1', '/path/to/key')
project2_storage_auto = gcloud.storage()
# Do things with the two "clients"
bucket1 = project1_storage.get_bucket('bucket-1')
bucket2 = project2_storage_auto.get_bucket('bucket-2')Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
api: coreauthtype: questionRequest for information or clarification. Not an issue.Request for information or clarification. Not an issue.
Activity
tseaver commentedon May 6, 2015
I honestly don't see the benefit to bundling project together with connection: it only gets passed to a couple of constructors (
storage.bucket.Bucket,pubsub.topic.Topic). "Burying" it in a client object doesn't feel like a win for such limited use. Thedatastore.dataset.Datasetcase is different, because a non-defaultdataset_idhas to be passed in lots of places.If we don't bundle the project, then we could just focus on adopting a common pattern for connection passing / lookup everywhere (#825 and related PRs).
tseaver commentedon May 6, 2015
Note that the work toward #825 makes #2 above (using a connection as a context manager) actually feasible.
dhermes commentedon May 6, 2015
It's probably worth it to create a list of all config that could be a candidate for a global (or membership in a long-lived object like a
Dataset).Right now we have
tseaver commentedon May 6, 2015
While the back-end pubsub API has
projectin all requests, our wrappers only require passing the project ID when constructing aTopicand when listing topics / subscriptions for a project: the other cases all get their project ID from the relevantTopicinstance.dhermes commentedon May 6, 2015
@tseaver Added this to my list
tseaver commentedon May 14, 2015
Issues about the "always use a Client" notion we discussed in Monday's call:
connection=Noneargument and the_require_connection()inside the method withclient=Noneand_require_client(), all FBO developers who a) don't want to use the default project ID, and b) don't want to pass it to theBucket/Topicconstructors or thelist_buckets/list_topics/list_subscriptionsqueries.Client, dropping all the intermediate classes in favor of pure imperative code.jgeewax commentedon May 14, 2015
@tseaver, hate to make more work for you, but is it crazy to toss in short examplar code snippets illustrating the problem ?
Some questions I'm having trouble answering based on our conversations...
Is it bad form to have objects know about their "parent" (
Blobknowing about it'sBucket, which knows about it'sClient; orEntity+Keyknowing about theirDatasetwhich knows about itsClient) ? If so, why?Is it bad form to make things involving the parent happen on that parent rather than the child? For example...
and then
Is it terrible if batches only permit a single client at a time? That is...
You mentioned that if we go the client route, the "operate on the module" idea is not a good one. I don't have a huge preference, but I'm curious why it'd be horrible to offer an import-time constructed client if they want to "just use the default everything". For example...
(I have no problems with us skipping this, but I wanted to ask....)
I get that this is an enormous number of questions, so if it's going to make GitHub explode and make everything unreadable, let's pick one of them for now and go with that...
tseaver commentedon May 14, 2015
I think there are different cases:
Bucketinstance is tied to a project (server-side), but not necessarily to a connection: e.g., one might have a connection whose credentials / scopes provided only read access to a given bucket, and another connection whose credentials / scopes had owner access. Also note that the project is only needed when instantiating aBucketwhich does not yet exist server-side: existing buckets know their project, but the library user won't have to deal with it.Blobinstance is necessarily tied to a bucket due to the server-side API. There is no conceivable access pattern which allows using a blob with a foreign bucket. In addition, nearly all theBlobmethods are implemented usingself.bucket.Keyinstance knows itsdataset_id, but needs nothing else from a "parent" object. Again, one might be using a key across different connections in order to take advantage of different credentials / scopes. AnEntityinstance holds itsKey, but needs nothing beyond it (no connection, for instance).Topicinstance is tied to a project at creation time. No other methods require / allow the project, however. Also like a bucket, a topic could be used across multiple connections.Subscriptioninstance is intrinsically tied to its topic, and uses it to implement its methods (mostly to compute its path).tseaver commentedon May 14, 2015
At the moment, one can use a batch or a transaction as a context manager an not pass it everywhere, or call methods through it. E.g.:
This works because the
_require_connectioncode is willing to look up the current batch / transaction from a thread-local stack, falling back to use the global default. Calling API functions through the notionalClientinstance will defeat that stacking. Note that a object methods will be also lose that stacking.Per #825, all of the methods / functions that trigger remote API calls now take an optional
connectionargument (modulo #858 which isn't yet merged). They all fall back to using either the "stack" (for batch/transaction support) or the global default. If we implement "always use a client", then we would have to undo all that work (which cleaned up the code a lot, and go back to usingself.connectionattributes / properties.It is a bit of a strawman, but having "always use a client" as the access pattern reduces the desirability of the various client-side objects: they become basically thin wrappers over the client.
jgeewax commentedon May 14, 2015
I'm proposing that we make people be a bit more explicit for batches and transactions:
Wouldn't this make the "require connection" stuff just go away all together? We never guess, or look to the global scope, or anything like that...
49 remaining items
tseaver commentedon May 21, 2015
Like Danny, I've run out of steam to argue.
The reason that I suggested starting a DX document is that I think it will either make the concerns I have obvious, or else make it clear that they can be addressed cleanly.
At the moment, the only way I can see to cleanly implement clients without re-welding them in to every class we just cleaned up, is to have the clients work at a layer "above" the classes we have now, with proxies for the "real" objects that pass in the connection from the client to every call that takes one. Maybe we can come up with a cleaner implementation than that, if we have a specific outline of how the client objects get used.
chore: move samples from python-docs-sample (#45)
build(deps): bump jinja2 from 3.1.4 to 3.1.5 in /.kokoro (#861)
build(deps): bump jinja2 from 3.1.4 to 3.1.5 in /.kokoro (#861)
chore(main): release 2.22.0 (#861)
feat: add async samples (#861)
chore: release 0.49.0 (#919)
chore(librarian): clean up owlbot files and pin image sha (#861)
chore: release 2.1.0 (#861)
docs: Mark revision_id in CommitSchemaRevisionRequest as deprecated (#…
chore(python): update dependencies in /.kokoro (#861)
chore(python): exclude setup.py in renovate config (#861)
build(deps): bump certifi from 2022.9.24 to 2022.12.7 in /synthtool/g…