Allow SDK key/config to be changed after client has been initialized.#64
Conversation
|
|
||
|
|
||
| def get(): | ||
| def get(sdk_key=None): |
There was a problem hiding this comment.
This is the least terrible pattern I could think of: first call get(sdk_key), then on subsequent calls it is optional..
I still don't like it though
|
[ch138] |
| ldclient.sdk_key = 'sdk_key' | ||
| ldclient.start_wait = 10 | ||
| client = ldclient.get() | ||
| ldclient.set_sdk_key('YOUR_SDK_KEY') |
There was a problem hiding this comment.
Example of new usage pattern.
| # 2 use cases: | ||
| # 1. Initial setup: sets the sdk key for the uninitialized client | ||
| # 2. Allows on-the-fly changing of the sdk key. When this function is called after the client has been initialized | ||
| # the client will get re-initialized with the new sdk key. In order for this to work, the return value of |
There was a problem hiding this comment.
This is the pitfall of this approach, but I don't see another way to do it. any ideas @pkaeding ?
There was a problem hiding this comment.
I don't think we should say that it should never be assigned; that seems a bit extreme. It is fine to get the client at the beginning of a request handler that needs to get a bunch of flags. You just don't want to keep the reference long-term.
On the other hand, we believe the population of users who need to change the SDK key at runtime is rather small. So most people can disregard this warning altogether if they like. I think we can re-word this to be less scary, while still explaining enough of the implications of different use cases.
There was a problem hiding this comment.
I'm almost inclined to avoid this wording, except when documenting how to change the config in an already-running app.
| from cachecontrol import CacheControl | ||
| from threading import Lock | ||
|
|
||
| GET_LATEST_FEATURES_PATH = '/sdk/latest-flags' |
There was a problem hiding this comment.
config code now in its own file.
There was a problem hiding this comment.
In order to make this change backwards compatible, I think you could alias the Config class in this package
| @@ -0,0 +1,171 @@ | |||
| from ldclient.event_consumer import EventConsumerImpl | |||
There was a problem hiding this comment.
The refactored Config class is as immutable as python allows..
| global __config | ||
| global __client | ||
| global __lock | ||
| if sdk_key is __config.sdk_key: |
There was a problem hiding this comment.
this should probably be guarded by the read lock, right?
| global client | ||
| _lock.lock() | ||
| if not client: | ||
| global __client |
There was a problem hiding this comment.
I don't think this is needed, since you declared global __client above.
|
|
||
|
|
||
| # the return value should not be assigned. | ||
| def get(): |
There was a problem hiding this comment.
In the README, you give an example of:
ldclient.get("your sdk key")
Should this be:
def get(sdk_key=None):
There was a problem hiding this comment.
apologies- readme is not up to date.
|
|
||
| class LDClient(object): | ||
| def __init__(self, sdk_key, config=None, start_wait=5): | ||
| def __init__(self, config=None, start_wait=5): |
There was a problem hiding this comment.
To try to make this backwards-compatible, I think you can also allow the sdk_key to be passed to this ctor, and apply it to the config if it is passed in.
If there is some reason not to do that (that I'm missing), then the config argument should not be optional-- it is not valid to create a client with no SDK key.
There was a problem hiding this comment.
it is valid to create an offline client with no sdk key, or a client with a redis feature store (events of course will fail or can be disabled)
There was a problem hiding this comment.
Also, directly creating a client is no longer the suggested route, so I prefer to have it only take in the config object.
There was a problem hiding this comment.
Ahh, good point about the offline client. I get that we don't want to encourage people to directly create the client, but it might ease the transition if they could drop in the new version, and have everything work as it did before, modulo some deprecation warnings that they can then work to eliminate.
|
|
||
| class EventConsumerImpl(Thread, EventConsumer): | ||
| def __init__(self, event_queue, sdk_key, config): | ||
| def __init__(self, event_queue, config): |
There was a problem hiding this comment.
This (and below) is a little harder to rationalize the backwards-compatible way of overlaying an optional sdk_key argument on the config.
Maybe doing that, and marking it deprecated would be the way to go? Then we can remove it in the next major release.
There was a problem hiding this comment.
These changes necesitate a major release (due to changes in singleton init flow). Does this change your take on this?
|
I think this approach makes sense. I would like to see if we can possibly do it in a backwards-compatible way, to avoid needing a major release, and make upgrading easier for our users. I'm not totally sure if that is possible, though. |
| __lock.unlock() | ||
|
|
||
|
|
||
| def init(): |
There was a problem hiding this comment.
My thinking here is that this could be called in server init code to pre-warm the client so the first request doesn't incur the penalty of connecting to the stream etc... on second thought I have removed it. Thoughts?
…th previous versions.
| check_uwsgi() | ||
| self._config = config or Config.default() | ||
|
|
||
| if config is not None and sdk_key is not None: |
There was a problem hiding this comment.
I think this should check if the skd_key is set in both places, not if any config is passed in. Some existing clients might be using a modified config (changed timeouts, etc), but passing in the sdk_key separately.
There was a problem hiding this comment.
good point- this could break existing patterns..
…th previous versions.
|
lgtm |
|
👍 |
The reasoning behind this is that it makes the reset SDK Key feature much more managable when a dynamic configuration is in place.
The intention is that this is an api-breaking change for anyone using the new singleton flow found in init.py, but anyone still using the client constructor should not have to make any code changes.