Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prepare 6.11.3 release #138

Merged
merged 253 commits into from
Dec 30, 2019
Merged
Changes from 1 commit
Commits
Show all changes
253 commits
Select commit Hold shift + click to select a range
1fc23e4
use expiringdict from PyPi
eli-darkly Oct 14, 2018
0a30e0d
Merge pull request #77 from launchdarkly/eb/ch24449/less-metadata-2
eli-darkly Oct 15, 2018
103b339
Merge pull request #78 from launchdarkly/eb/ch25286/expiring-dict
eli-darkly Oct 15, 2018
40f2ca4
merge from public after release
LaunchDarklyCI Oct 17, 2018
ae8b25e
implement file data source, not including auto-update
eli-darkly Nov 1, 2018
850837d
rm debugging
eli-darkly Nov 1, 2018
aa7684a
rm debugging
eli-darkly Nov 1, 2018
39c9042
Python 3 compatibility fix
eli-darkly Nov 1, 2018
a43bf0c
add file watching, update documentation and tests
eli-darkly Nov 2, 2018
2cea730
readme
eli-darkly Nov 2, 2018
dcf1afe
debugging
eli-darkly Nov 2, 2018
4e98fdd
debugging
eli-darkly Nov 2, 2018
8f3c221
debugging
eli-darkly Nov 2, 2018
84276dd
fix cleanup logic
eli-darkly Nov 2, 2018
2a822e6
rm debugging
eli-darkly Nov 2, 2018
eaabe4d
Merge pull request #79 from launchdarkly/eb/ch26233/file-data-source
eli-darkly Nov 14, 2018
ac5e8de
typo in comment
eli-darkly Nov 14, 2018
39f5f62
merge from public after release
LaunchDarklyCI Nov 14, 2018
040ced9
add feature store wrapper class and make Redis feature store use it
eli-darkly Dec 29, 2018
59a67a8
test the new Redis factory method
eli-darkly Dec 29, 2018
1e38ac1
add DynamoDB support
eli-darkly Dec 29, 2018
431dddf
add test credentials
eli-darkly Dec 29, 2018
3aa5644
link in comment
eli-darkly Dec 31, 2018
bd00276
comment
eli-darkly Dec 31, 2018
11eabd3
Merge branch 'eb/ch28329/feature-store-support' into eb/ch28329/dynamodb
eli-darkly Dec 31, 2018
534ec5d
don't catch exceptions in Redis feature store, let the client catch them
eli-darkly Dec 31, 2018
5f16c8d
gitignore
eli-darkly Dec 31, 2018
ac0f2ea
misc test fixes
eli-darkly Dec 31, 2018
fa56526
Merge branch 'eb/ch28329/feature-store-support' into eb/ch28329/dynamodb
eli-darkly Dec 31, 2018
3a1c2dc
Merge pull request #81 from launchdarkly/eb/ch28329/dynamodb
eli-darkly Jan 9, 2019
b06eef9
Merge pull request #80 from launchdarkly/eb/ch28329/feature-store-sup…
eli-darkly Jan 9, 2019
256b6fb
implement dependency ordering for feature store data
eli-darkly Jan 9, 2019
289077c
fix incomplete implementation & test
eli-darkly Jan 9, 2019
2c59294
Python 3.x fix
eli-darkly Jan 9, 2019
1dd6961
Merge pull request #82 from launchdarkly/eb/ch29197/dependency-order
eli-darkly Jan 15, 2019
78b6118
minor doc fixes
eli-darkly Jan 16, 2019
931d008
merge from public after release
LaunchDarklyCI Jan 16, 2019
3eb821c
feature store test improvements
eli-darkly Jan 18, 2019
cc938e3
better database prefix test
eli-darkly Jan 19, 2019
5b8b337
clarify comment
eli-darkly Jan 19, 2019
b911d9e
Merge pull request #83 from launchdarkly/eb/feature-store-tests
eli-darkly Jan 19, 2019
f9ce243
add Consul feature store integration
eli-darkly Jan 19, 2019
89a96be
typo
eli-darkly Jan 19, 2019
da8c1a6
rm extra import
eli-darkly Jan 19, 2019
b19e618
fix byte/string issue and rename file
eli-darkly Jan 19, 2019
db621dc
rename file
eli-darkly Jan 19, 2019
b09e07e
docs
eli-darkly Jan 19, 2019
9ea89ca
script typo
eli-darkly Jan 19, 2019
a50e6f3
move all low-level feature store integration code into submodules
eli-darkly Jan 19, 2019
68c55b4
Merge pull request #84 from launchdarkly/eb/ch28431/consul
eli-darkly Jan 26, 2019
0baddab
move file data source implementation
eli-darkly Jan 28, 2019
c8585ba
don't need future.with_statement in Python 2.6+
eli-darkly Jan 28, 2019
2a6d53b
don't need future.absolute_import in Python 2.6+
eli-darkly Jan 28, 2019
c32793a
don't need future.print_function when you're printing a single string…
eli-darkly Jan 28, 2019
4971d17
don't need future.division since we're not using the / operator
eli-darkly Jan 28, 2019
0abadf1
rm unused dependency
eli-darkly Jan 28, 2019
e228e90
Revert "rm unused dependency"
eli-darkly Jan 28, 2019
122d7a6
don't need builtins.object unless we're defining an iterator, and eve…
eli-darkly Jan 29, 2019
6a45e70
update docs with note on portability
eli-darkly Jan 29, 2019
858e001
typo
eli-darkly Jan 29, 2019
6788526
Merge pull request #86 from launchdarkly/eb/ch30683/future
eli-darkly Jan 29, 2019
5657142
Merge pull request #85 from launchdarkly/eb/move-modules
eli-darkly Jan 31, 2019
d4d4b8a
update package metadata prior to release
eli-darkly Jan 31, 2019
7a6db77
Merge pull request #87 from launchdarkly/eb/ch30988/setup-py
eli-darkly Jan 31, 2019
4787d44
merge from public after release
LaunchDarklyCI Jan 31, 2019
eaf677e
add test for whether the package can be installed
eli-darkly Jan 31, 2019
eae17f6
fix build
eli-darkly Jan 31, 2019
0892c97
fix build
eli-darkly Jan 31, 2019
e21aa65
fix manifest
eli-darkly Jan 31, 2019
0520a9b
skip test on 3.3
eli-darkly Jan 31, 2019
9deef2d
misc fixes for 3.3
eli-darkly Jan 31, 2019
e44abfa
another 3.3 fix
eli-darkly Jan 31, 2019
273219a
misc fixes
eli-darkly Jan 31, 2019
b3944f9
misc fixes
eli-darkly Jan 31, 2019
02dad33
Merge branch 'eb/ch31016/test-packaging' into eb/ch31016/fix-packaging
eli-darkly Jan 31, 2019
5706f21
Merge pull request #89 from launchdarkly/eb/ch31016/fix-packaging
eli-darkly Jan 31, 2019
9ac4c3b
Merge pull request #88 from launchdarkly/eb/ch31016/test-packaging
eli-darkly Jan 31, 2019
60aa805
merge from public after release
LaunchDarklyCI Jan 31, 2019
b4792e6
add test for importing the built package
eli-darkly Jan 31, 2019
7c2b501
add submodule imports
eli-darkly Jan 31, 2019
a9d481c
ensure that all packages are included in distribution
eli-darkly Jan 31, 2019
71b821f
fix import
eli-darkly Jan 31, 2019
424db63
Merge pull request #91 from launchdarkly/eb/ch31044/package-subdirs-fix
eli-darkly Jan 31, 2019
b2e6c59
Merge pull request #90 from launchdarkly/eb/ch31044/test-package-import
eli-darkly Jan 31, 2019
bee50e8
merge from public after release
LaunchDarklyCI Jan 31, 2019
06fc3b2
add basic pipeline and install deps
hroederld Feb 5, 2019
0165540
add pytest
hroederld Feb 5, 2019
7c9f4e2
remove explicit install of deps
hroederld Feb 5, 2019
a38b957
add other db deps
hroederld Feb 5, 2019
0b6d28f
major cleanup of doc comments, add Sphinx build script
eli-darkly Feb 5, 2019
71534aa
add consul to test-requirements, remove specific reference to install…
hroederld Feb 5, 2019
6bbd65f
Revert "add consul to test-requirements, remove specific reference to…
hroederld Feb 5, 2019
0ec55a7
remove redis and dynamo explicit dep reference
hroederld Feb 5, 2019
49c5993
add requirements.txt
eli-darkly Feb 5, 2019
5228df7
add config file
eli-darkly Feb 5, 2019
f4e5c86
break up API docs into logical groups with a better home page
eli-darkly Feb 5, 2019
9445a6e
misc cleanup
eli-darkly Feb 5, 2019
c496c35
misc cleanup
eli-darkly Feb 5, 2019
a6f1bca
Merge pull request #93 from launchdarkly/eb/ch17280/docs
eli-darkly Feb 5, 2019
68bb4e4
RTD config fixes
eli-darkly Feb 5, 2019
6766920
minor edit
eli-darkly Feb 5, 2019
f612360
Merge pull request #94 from launchdarkly/eb/ch17280/doc-templates
eli-darkly Feb 5, 2019
74e82c8
misc. doc comment edits
eli-darkly Feb 6, 2019
9316b0d
use RTD theme
eli-darkly Feb 6, 2019
794e59f
Merge pull request #92 from launchdarkly/hr/azure
hroederld Feb 6, 2019
5df3b55
Merge pull request #95 from launchdarkly/eb/ch17280/edits
eli-darkly Feb 6, 2019
5eee9ba
Merge pull request #96 from launchdarkly/eb/ch17280/rtd-theme
eli-darkly Feb 6, 2019
338910c
remove jsonpickle
eli-darkly Feb 11, 2019
f586cd1
misc doc comment/readme edits prior to publishing docs
eli-darkly Feb 11, 2019
125d359
Merge pull request #97 from launchdarkly/eb/ch31867/jsonpickle
eli-darkly Feb 12, 2019
0972671
Merge pull request #98 from launchdarkly/eb/ch17280/doc-edits
eli-darkly Feb 12, 2019
dc1f394
merge from public after release
LaunchDarklyCI Feb 12, 2019
9731f4d
add git placeholders for unused dirs
eli-darkly Feb 13, 2019
acfe58e
Merge branch 'master' of github.com:launchdarkly/python-client
eli-darkly Feb 13, 2019
87336db
use default theme
eli-darkly Feb 13, 2019
c7a67dc
Merge branch 'master' of github.com:launchdarkly/python-client
eli-darkly Feb 13, 2019
2dedbc4
add experimentation event overrides for rules and fallthrough
eli-darkly Feb 25, 2019
6846ba1
a little more test coverage
eli-darkly Feb 25, 2019
c514216
rm unnecessary logic
eli-darkly Feb 26, 2019
afab05d
more factory methods
eli-darkly Feb 26, 2019
e0c563c
Merge pull request #99 from launchdarkly/eb/ch32305/experiment
eli-darkly Feb 28, 2019
84198a3
try python -m instead of pytest directly
hroederld Mar 1, 2019
80411dd
add setuptools
hroederld Mar 1, 2019
52c0a19
use python -m for all of pip
hroederld Mar 1, 2019
5bdea5f
add UsePythonVersion task
hroederld Mar 1, 2019
60a66a8
fix indent
hroederld Mar 1, 2019
1907d75
remove manually adding setuptools
hroederld Mar 1, 2019
7cdf9fc
add on 3.7 stages
hroederld Mar 2, 2019
1023d45
fix mkdir for reports
hroederld Mar 2, 2019
b9778b6
upload test artifacts
hroederld Mar 2, 2019
945fc07
Merge pull request #100 from launchdarkly/hr/azurepytest
hroederld Mar 2, 2019
7738c99
Merge commit '327aaac4a5c419ea45d3723d4a87309f67da4a40'
eli-darkly Mar 13, 2019
e5d5e41
skip trying to load pyyaml in Python 3.3
eli-darkly Mar 26, 2019
fd883cd
can't use watchdog in Python 3.3
eli-darkly Mar 26, 2019
b3dc4c4
mark test as skipped
eli-darkly Mar 26, 2019
bd4daf7
Merge pull request #101 from launchdarkly/eb/ch34481/pyyaml-py3.3
eli-darkly Mar 26, 2019
803a794
coerce user attributes into strings when necessary, don't send events…
eli-darkly Mar 29, 2019
b7035a5
more unit tests
eli-darkly Mar 29, 2019
44101b2
remove redundant sanitize step
eli-darkly Mar 29, 2019
4b8ee84
Merge pull request #102 from launchdarkly/eb/ch35206/stringify-attrs
eli-darkly Mar 29, 2019
dd67a7c
merge from public after release
LaunchDarklyCI Mar 29, 2019
ddfb3c2
ensure that client components are cleaned up correct in every configu…
eli-darkly Apr 9, 2019
4ca26c7
Merge pull request #103 from launchdarkly/eb/ch36211/close-ldd
eli-darkly Apr 9, 2019
7585684
miscellaneous test fixes
eli-darkly Apr 9, 2019
a164906
Merge pull request #104 from launchdarkly/eb/fix-tests
eli-darkly Apr 9, 2019
143b66a
merge from public after release
LaunchDarklyCI Apr 10, 2019
6763b54
Merge branch 'master' into experiment
eli-darkly Apr 13, 2019
3b16ebf
support metric value with track()
eli-darkly Apr 13, 2019
2f6961d
update method description
eli-darkly Apr 17, 2019
a166adb
Merge pull request #105 from launchdarkly/eb/ch32305/metric-value
eli-darkly Apr 17, 2019
950d2b3
Merge branch 'master' of github.com:launchdarkly/python-client
eli-darkly Apr 24, 2019
902be02
update readme format and repo links
eli-darkly Apr 26, 2019
f41f2cc
allow unit tests to be run without databases
eli-darkly Apr 26, 2019
d764fd8
add missing test
eli-darkly Apr 26, 2019
91b1250
Merge pull request #107 from launchdarkly/eb/ch37610/no-db-tests
eli-darkly Apr 26, 2019
ea5d8e8
rm FOSSA link/badge
eli-darkly Apr 26, 2019
6ed12f1
misc fixes
eli-darkly Apr 26, 2019
6cd745f
Merge pull request #106 from launchdarkly/eb/ch36754/readme-update
eli-darkly Apr 26, 2019
1fba96f
merge from public after release
LaunchDarklyCI Apr 26, 2019
cbac044
minor doc link fix
eli-darkly Apr 26, 2019
52c3b23
fix skipping of database tests
eli-darkly May 1, 2019
6d105a9
Merge pull request #109 from launchdarkly/eb/ch37610/fix-skip-db-tests
eli-darkly May 1, 2019
6161055
renaming the package to launchdarkly-server-sdk (#108)
bwoskow-ld May 1, 2019
47f2e3e
Merge branch 'master' of github.com:launchdarkly/python-server-sdk
bwoskow-ld May 2, 2019
ac3b1e8
merge from public after release
LaunchDarklyCI May 2, 2019
b9011c0
Merge branch 'master' of github.com:launchdarkly/python-server-sdk
bwoskow-ld May 3, 2019
83450d9
Merge branch 'master' of github.com:launchdarkly/python-server-sdk-pr…
bwoskow-ld May 3, 2019
34b15f5
use log.warning(), not log.warn() or warnings.warn()
eli-darkly Jun 11, 2019
3a03ea5
Merge pull request #110 from launchdarkly/eb/ch40725/log-warnings
eli-darkly Jun 11, 2019
b472691
merge from public after release
LaunchDarklyCI Jun 11, 2019
c990266
drop events when inbox is full
eli-darkly Aug 19, 2019
e436f77
rm obsolete pytest.raises parameter
eli-darkly Aug 19, 2019
1e068c9
clean up test state management
eli-darkly Aug 20, 2019
4b74fcf
typo
eli-darkly Aug 20, 2019
e5dd5ba
Merge branch 'master' of github.com:launchdarkly/python-server-sdk
eli-darkly Aug 20, 2019
71a24f2
Merge branch 'master' into experiment
eli-darkly Aug 20, 2019
be6ee0d
Merge pull request #111 from launchdarkly/eb/ch42975/inbox-full
eli-darkly Aug 20, 2019
623687d
merge from public after release
LaunchDarklyCI Aug 20, 2019
0b0b636
Merge branch 'master' of github.com:launchdarkly/python-server-sdk
eli-darkly Aug 20, 2019
03f7a86
merge from public after release
eli-darkly Aug 20, 2019
16f3d43
Merge branch 'experiment'
eli-darkly Aug 20, 2019
ee7a51c
store the package version in just one place
eli-darkly Aug 20, 2019
1c10e1e
fix package reference
eli-darkly Aug 20, 2019
d9c96dd
add requirements
eli-darkly Aug 20, 2019
73d20f7
don't import ldclient.version directly
eli-darkly Aug 20, 2019
0a0aa8f
Revert "add requirements"
eli-darkly Aug 20, 2019
11f0da6
fix merge error + adjust for some event properties now being optional
eli-darkly Aug 20, 2019
17bfa5a
fix summary logic again for now-optional event properties
eli-darkly Aug 20, 2019
8bb3375
merge from public after release
LaunchDarklyCI Aug 20, 2019
67eb763
merge from public after release
eli-darkly Aug 20, 2019
53b981a
Merge branch 'master' into eb/test-cleanup
eli-darkly Aug 20, 2019
500fa28
Merge branch 'master' into eb/package-version
eli-darkly Aug 20, 2019
42ad0be
Merge pull request #112 from launchdarkly/eb/test-cleanup
eli-darkly Aug 20, 2019
1154981
Merge pull request #114 from launchdarkly/eb/package-version
eli-darkly Aug 20, 2019
a5da010
Allow explicitly proxying only ld requests (#130)
gangeli Oct 25, 2019
69f2233
fix broken indirect/patch request, add tests for feature requestor
eli-darkly Oct 28, 2019
0fa5e05
Python 2/3 compatibility for HTTPServer
eli-darkly Oct 28, 2019
e75ff0f
Py2/3 compatibility: queue
eli-darkly Oct 28, 2019
68161a2
more Py3 compatibility
eli-darkly Oct 28, 2019
74c9eed
don't need import of builtins
eli-darkly Oct 28, 2019
032b04c
fix string encoding
eli-darkly Oct 28, 2019
89ce3e2
implement setting proxy URL by environment variable
eli-darkly Oct 29, 2019
ae764b5
rm debugging
eli-darkly Oct 29, 2019
28ee4b5
fix autodoc options to exclude magic methods
eli-darkly Oct 29, 2019
4cbcd30
Merge pull request #115 from launchdarkly/eb/ch54217/indirect-patch
eli-darkly Oct 30, 2019
9fb16a6
Merge pull request #117 from launchdarkly/eb/ch38000/fix-doc-config
eli-darkly Oct 30, 2019
d47c315
Merge pull request #116 from launchdarkly/eb/ch53090/proxy-var
eli-darkly Oct 30, 2019
3dddca2
merge from public after release
LaunchDarklyCI Oct 30, 2019
9435aee
add config option for proxy URL
eli-darkly Oct 31, 2019
4fc6ce7
comment
eli-darkly Oct 31, 2019
00432be
add end-to-end unit tests for proxy config
eli-darkly Oct 31, 2019
5911fd9
indents
eli-darkly Oct 31, 2019
ba52cae
Merge pull request #118 from launchdarkly/eb/ch52414/proxy-config
eli-darkly Oct 31, 2019
28b22b8
merge from public after release
LaunchDarklyCI Oct 31, 2019
35faf7d
Merge branch 'master' of github.com:launchdarkly/python-server-sdk
bwoskow-ld Nov 7, 2019
63125f5
add 3.8 build
eli-darkly Nov 21, 2019
3c68cd2
image name
eli-darkly Nov 21, 2019
9b1adf3
fail on SyntaxWarning
eli-darkly Nov 21, 2019
6a954e3
typo
eli-darkly Nov 21, 2019
d6bf44c
command syntax
eli-darkly Nov 21, 2019
7ec6974
Merge pull request #119 from launchdarkly/eb/ch56849/py3.8
eli-darkly Nov 21, 2019
7fda088
Merge branch 'master' of github.com:launchdarkly/python-server-sdk
eli-darkly Nov 21, 2019
7b3177f
pin expiringdict dependency for Python 3.3 compatibility
eli-darkly Nov 21, 2019
9942d77
add Windows CircleCI job
eli-darkly Nov 21, 2019
38f3f43
periods are no longer valid in CircleCI job names
eli-darkly Nov 21, 2019
c969db2
syntax fix
eli-darkly Nov 21, 2019
bc31ec9
install Python in Windows
eli-darkly Nov 21, 2019
64486a3
set path
eli-darkly Nov 21, 2019
37509ff
move command
eli-darkly Nov 21, 2019
3b41766
turn off debug logging
eli-darkly Nov 21, 2019
ef68058
Py3 in Windows
eli-darkly Nov 21, 2019
0c93df7
config param
eli-darkly Nov 21, 2019
86d27a8
rm redundant step
eli-darkly Nov 21, 2019
001e196
choco switch
eli-darkly Nov 21, 2019
121ec89
Merge pull request #120 from launchdarkly/eb/ch55512/py3.3-deps
eli-darkly Nov 21, 2019
9905cd5
Merge branch 'master' into eb/ch56746/windows-ci
eli-darkly Nov 21, 2019
23a4222
refactor Linux jobs using CircleCI 2.1 features
eli-darkly Nov 21, 2019
a5aaa99
set log level before anything else
eli-darkly Nov 21, 2019
9e40321
rm Azure config
eli-darkly Nov 21, 2019
d69fba0
Merge pull request #121 from launchdarkly/eb/ch56746/windows-ci
eli-darkly Nov 21, 2019
3e9c68b
merge from public after release
LaunchDarklyCI Nov 21, 2019
669e772
use yaml.safe_load() to avoid code execution vulnerability in file da…
eli-darkly Dec 9, 2019
4483990
Merge pull request #122 from launchdarkly/eb/ch58025/yaml-load
eli-darkly Dec 9, 2019
8248bae
merge from public after release
LaunchDarklyCI Dec 10, 2019
7fd454f
don't let user fall outside of last bucket in rollout
bwoskow-ld Dec 24, 2019
588f352
fixing conditional logic
bwoskow-ld Dec 24, 2019
af5a162
fix off-by-1 error
eli-darkly Dec 30, 2019
75a9aab
avoid redundant dict lookups
eli-darkly Dec 30, 2019
590ca64
add unit tests for basic bucketing logic and edge case
eli-darkly Dec 30, 2019
8ce7cb8
Merge pull request #123 from launchdarkly/bw/ch43307/bucket-issue
eli-darkly Dec 30, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add file watching, update documentation and tests
eli-darkly committed Nov 2, 2018
commit a43bf0c56789f26f80199e260ca04c4b9cb6b918
194 changes: 188 additions & 6 deletions ldclient/file_data_source.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import six
import traceback

@@ -9,14 +10,119 @@
except ImportError:
pass

have_watchdog = False
try:
import watchdog
import watchdog.events
import watchdog.observers
have_watchdog = True
except ImportError:
pass

from ldclient.interfaces import UpdateProcessor
from ldclient.repeating_timer import RepeatingTimer
from ldclient.util import log
from ldclient.versioned_data_kind import FEATURES, SEGMENTS


class FileDataSource(UpdateProcessor):
@classmethod
def factory(cls, **kwargs):
"""Provides a way to use local files as a source of feature flag state. This would typically be
used in a test environment, to operate using a predetermined feature flag state without an
actual LaunchDarkly connection.
To use this component, call `FileDataSource.factory`, and store its return value in the
`update_processor_class` property of your LaunchDarkly client configuration. In the options
to `factory`, set `paths` to the file path(s) of your data file(s):
::
factory = FileDataSource.factory(paths: [ myFilePath ])
config = Config(update_processor_class = factory)
This will cause the client not to connect to LaunchDarkly to get feature flags. The
client may still make network connections to send analytics events, unless you have disabled
this with Config.send_events or Config.offline.
Flag data files can be either JSON or YAML (in order to use YAML, you must install the 'pyyaml'
package). They contain an object with three possible properties:
* "flags": Feature flag definitions.
* "flagValues": Simplified feature flags that contain only a value.
* "segments": User segment definitions.
The format of the data in "flags" and "segments" is defined by the LaunchDarkly application
and is subject to change. Rather than trying to construct these objects yourself, it is simpler
to request existing flags directly from the LaunchDarkly server in JSON format, and use this
output as the starting point for your file. In Linux you would do this:
::
curl -H "Authorization: {your sdk key}" https://app.launchdarkly.com/sdk/latest-all
The output will look something like this (but with many more properties):
::
{
"flags": {
"flag-key-1": {
"key": "flag-key-1",
"on": true,
"variations": [ "a", "b" ]
}
},
"segments": {
"segment-key-1": {
"key": "segment-key-1",
"includes": [ "user-key-1" ]
}
}
}
Data in this format allows the SDK to exactly duplicate all the kinds of flag behavior supported
by LaunchDarkly. However, in many cases you will not need this complexity, but will just want to
set specific flag keys to specific values. For that, you can use a much simpler format:
::
{
"flagValues": {
"my-string-flag-key": "value-1",
"my-boolean-flag-key": true,
"my-integer-flag-key": 3
}
}
Or, in YAML:
::
flagValues:
my-string-flag-key: "value-1"
my-boolean-flag-key: true
my-integer-flag-key: 1
It is also possible to specify both "flags" and "flagValues", if you want some flags
to have simple values and others to have complex behavior. However, it is an error to use the
same flag key or segment key more than once, either in a single file or across multiple files.
If the data source encounters any error in any file-- malformed content, a missing file, or a
duplicate key-- it will not load flags from any of the files.
:param kwargs:
See below
:Keyword arguments:
* **paths** (array): The paths of the source files for loading flag data. These may be absolute paths
or relative to the current working directory. Files will be parsed as JSON unless the 'pyyaml'
package is installed, in which case YAML is also allowed.
* **auto_update** (boolean): True if the data source should watch for changes to the source file(s)
and reload flags whenever there is a change. The default implementation of this feature is based on
polling the filesystem, which may not perform well; if you install the 'watchdog' package (not
included by default, to avoid adding unwanted dependencies to the SDK), its native file watching
mechanism will be used instead. Note that auto-updating will only work if all of the files you
specified have valid directory paths at startup time.
* **poll_interval** (float): The minimum interval, in seconds, between checks for file modifications -
used only if auto_update is true, and if the native file-watching mechanism from 'watchdog' is not
being used. The default value is 1 second.
"""
return lambda config, store, ready : FileDataSource(store, kwargs, ready)

def __init__(self, store, options, ready):
@@ -26,16 +132,25 @@ def __init__(self, store, options, ready):
self._paths = options.get('paths', [])
if isinstance(self._paths, six.string_types):
self._paths = [ self._paths ]

self._auto_update = options.get('auto_update', False)
self._poll_interval = options.get('poll_interval', 1)
self._force_polling = options.get('force_polling', False) # used only in tests

def start(self):
self._load_all()

if self._auto_update:
self._auto_updater = self._start_auto_updater()
else:
self._auto_updater = None

# We will signal readiness immediately regardless of whether the file load succeeded or failed -
# the difference can be detected by checking initialized()
self._ready.set()

def stop(self):
pass
if self._auto_updater:
self._auto_updater.stop()

def initialized(self):
return self._inited
@@ -66,10 +181,7 @@ def _load_file(self, path, all_data):

def _parse_content(self, content):
if have_yaml:
if content.strip().startswith("{"):
return json.loads(content)
else:
return yaml.load(content)
return yaml.load(content) # pyyaml correctly parses JSON too
return json.loads(content)

def _add_item(self, all_data, kind, item):
@@ -89,3 +201,73 @@ def _make_flag_with_value(self, key, value):
},
'variations': [ value ]
}

def _start_auto_updater(self):
resolved_paths = []
for path in self._paths:
try:
resolved_paths.append(os.path.realpath(path))
except:
log.warn('Cannot watch for changes to data file "%s" because it is an invalid path' % path)
if have_watchdog and not self._force_polling:
return FileDataSource.WatchdogAutoUpdater(resolved_paths, self._load_all)
else:
return FileDataSource.PollingAutoUpdater(resolved_paths, self._load_all, self._poll_interval)

# Watch for changes to data files using the watchdog package. This uses native OS filesystem notifications
# if available for the current platform.
class WatchdogAutoUpdater(object):
def __init__(self, resolved_paths, reloader):
watched_files = set(resolved_paths)

class LDWatchdogHandler(watchdog.events.FileSystemEventHandler):
def on_any_event(self, event):
if event.src_path in watched_files:
reloader()

dir_paths = set()
for path in resolved_paths:
dir_paths.add(os.path.dirname(path))

self._observer = watchdog.observers.Observer()
handler = LDWatchdogHandler()
for path in dir_paths:
self._observer.schedule(handler, path)
self._observer.start()

def stop(self):
self._observer.stop()
self._observer.join()

# Watch for changes to data files by polling their modification times. This is used if auto-update is
# on but the watchdog package is not installed.
class PollingAutoUpdater(object):
def __init__(self, resolved_paths, reloader, interval):
self._paths = resolved_paths
self._reloader = reloader
self._file_times = self._check_file_times()
self._timer = RepeatingTimer(interval, self._poll)
self._timer.start()

def stop(self):
self._timer.stop()

def _poll(self):
new_times = self._check_file_times()
changed = False
for file_path, file_time in six.iteritems(self._file_times):
if new_times.get(file_path) is not None and new_times.get(file_path) != file_time:
changed = True
break
self._file_times = new_times
if changed:
self._reloader()

def _check_file_times(self):
ret = {}
for path in self._paths:
try:
ret[path] = os.path.getmtime(path)
except:
ret[path] = None
return ret
1 change: 1 addition & 0 deletions test-requirements.txt
Original file line number Diff line number Diff line change
@@ -6,3 +6,4 @@ pytest-capturelog>=0.7
pytest-cov>=2.4.0
codeclimate-test-reporter>=0.2.1
pyyaml>=3.0
watchdog>=0.9
76 changes: 50 additions & 26 deletions testing/test_file_data_source.py
Original file line number Diff line number Diff line change
@@ -78,19 +78,19 @@
}
'''

fds = None
data_source = None
store = None
ready = None


def setup_function():
global fds, store, ready
global data_source, store, ready
store = InMemoryFeatureStore()
ready = threading.Event()

def teardown_function():
if fds is not None:
fds.stop()
if data_source is not None:
data_source.stop()

def make_temp_file(content):
f, path = tempfile.mkstemp()
@@ -105,18 +105,18 @@ def replace_file(path, content):
def test_does_not_load_data_prior_to_start():
path = make_temp_file('{"flagValues":{"key":"value"}}')
try:
fds = FileDataSource.factory(paths = path)(Config(), store, ready)
data_source = FileDataSource.factory(paths = path)(Config(), store, ready)
assert ready.is_set() is False
assert fds.initialized() is False
assert data_source.initialized() is False
assert store.initialized is False
finally:
os.remove(path)

def test_loads_flags_on_start_from_json():
path = make_temp_file(all_properties_json)
try:
fds = FileDataSource.factory(paths = path)(Config(), store, ready)
fds.start()
data_source = FileDataSource.factory(paths = path)(Config(), store, ready)
data_source.start()
assert store.initialized is True
assert sorted(list(store.all(FEATURES, lambda x: x).keys())) == all_flag_keys
finally:
@@ -125,8 +125,8 @@ def test_loads_flags_on_start_from_json():
def test_loads_flags_on_start_from_yaml():
path = make_temp_file(all_properties_yaml)
try:
fds = FileDataSource.factory(paths = path)(Config(), store, ready)
fds.start()
data_source = FileDataSource.factory(paths = path)(Config(), store, ready)
data_source.start()
assert store.initialized is True
assert sorted(list(store.all(FEATURES, lambda x: x).keys())) == all_flag_keys
finally:
@@ -135,26 +135,26 @@ def test_loads_flags_on_start_from_yaml():
def test_sets_ready_event_and_initialized_on_successful_load():
path = make_temp_file(all_properties_json)
try:
fds = FileDataSource.factory(paths = path)(Config(), store, ready)
fds.start()
assert fds.initialized() is True
data_source = FileDataSource.factory(paths = path)(Config(), store, ready)
data_source.start()
assert data_source.initialized() is True
assert ready.is_set() is True
finally:
os.remove(path)

def test_sets_ready_event_and_does_not_set_initialized_on_unsuccessful_load():
bad_file_path = 'no-such-file'
fds = FileDataSource.factory(paths = bad_file_path)(Config(), store, ready)
fds.start()
assert fds.initialized() is False
data_source = FileDataSource.factory(paths = bad_file_path)(Config(), store, ready)
data_source.start()
assert data_source.initialized() is False
assert ready.is_set() is True

def test_can_load_multiple_files():
path1 = make_temp_file(flag_only_json)
path2 = make_temp_file(segment_only_json)
try:
fds = FileDataSource.factory(paths = [ path1, path2 ])(Config(), store, ready)
fds.start()
data_source = FileDataSource.factory(paths = [ path1, path2 ])(Config(), store, ready)
data_source.start()
assert len(store.all(FEATURES, lambda x: x)) == 1
assert len(store.all(SEGMENTS, lambda x: x)) == 1
finally:
@@ -165,8 +165,8 @@ def test_does_not_allow_duplicate_keys():
path1 = make_temp_file(flag_only_json)
path2 = make_temp_file(flag_only_json)
try:
fds = FileDataSource.factory(paths = [ path1, path2 ])(Config(), store, ready)
fds.start()
data_source = FileDataSource.factory(paths = [ path1, path2 ])(Config(), store, ready)
data_source.start()
assert len(store.all(FEATURES, lambda x: x)) == 0
finally:
os.remove(path1)
@@ -175,8 +175,8 @@ def test_does_not_allow_duplicate_keys():
def test_does_not_reload_modified_file_if_auto_update_is_off():
path = make_temp_file(flag_only_json)
try:
fds = FileDataSource.factory(paths = path)(Config(), store, ready)
fds.start()
data_source = FileDataSource.factory(paths = path)(Config(), store, ready)
data_source.start()
assert len(store.all(SEGMENTS, lambda x: x)) == 0
time.sleep(0.5)
replace_file(path, segment_only_json)
@@ -185,22 +185,46 @@ def test_does_not_reload_modified_file_if_auto_update_is_off():
finally:
os.remove(path)

def do_auto_update_test(options):
path = make_temp_file(flag_only_json)
options['paths'] = path
try:
data_source = FileDataSource.factory(**options)(Config(), store, ready)
data_source.start()
assert len(store.all(SEGMENTS, lambda x: x)) == 0
time.sleep(0.5)
replace_file(path, segment_only_json)
time.sleep(0.5)
assert len(store.all(SEGMENTS, lambda x: x)) == 1
finally:
os.remove(path)

def test_reloads_modified_file_if_auto_update_is_on():
do_auto_update_test({ 'auto_update': True })

def test_reloads_modified_file_in_polling_mode():
do_auto_update_test({ 'auto_update': True, 'force_polling': True, 'poll_interval': 0.1 })

def test_evaluates_full_flag_with_client_as_expected():
path = make_temp_file(all_properties_json)
try:
fds = FileDataSource.factory(paths = path)
client = LDClient(config=Config(update_processor_class = fds, send_events = False))
data_source = FileDataSource.factory(paths = path)
client = LDClient(config=Config(update_processor_class = data_source, send_events = False))
value = client.variation('flag1', { 'key': 'user' }, '')
assert value == 'on'
finally:
os.remove(path)
if client is not None:
client.close()

def test_evaluates_simplified_flag_with_client_as_expected():
path = make_temp_file(all_properties_json)
try:
fds = FileDataSource.factory(paths = path)
client = LDClient(config=Config(update_processor_class = fds, send_events = False))
data_source = FileDataSource.factory(paths = path)
client = LDClient(config=Config(update_processor_class = data_source, send_events = False))
value = client.variation('flag2', { 'key': 'user' }, '')
assert value == 'value2'
finally:
os.remove(path)
if client is not None:
client.close()