Skip to content

Commit

Permalink
Merge 5f9d337 into 4d41295
Browse files Browse the repository at this point in the history
  • Loading branch information
BananaWanted committed Mar 6, 2020
2 parents 4d41295 + 5f9d337 commit 3d51b0c
Show file tree
Hide file tree
Showing 22 changed files with 583 additions and 31 deletions.
146 changes: 146 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: CI
on:
push:
branches:
- master
pull_request: {}

jobs:
js:
runs-on: ubuntu-latest
strategy:
matrix:
node: [ '12', '10' ]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- uses: actions/cache@v1
id: node-modules
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node }}-node-modules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-modules-
- run: |
yarn install
yarn build
yarn lerna bootstrap
yarn lint
yarn test
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true
path-to-lcov: ./coverage/lcov.info # optional (default value)

py:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip
uses: actions/cache@v1
with:
path: ~/.cache/pip # This path is specific to Ubuntu
# Look to see if there is a cache hit for the corresponding requirements file
key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('packages/py-ab-testing/*') }}
restore-keys: |
${{ runner.os }}-${{ matrix.python-version }}-pip-
- name: Install dependencies
working-directory: ./packages/py-ab-testing
run: |
python -m pip install --upgrade pip setuptools wheel
- name: Lint with flake8
working-directory: ./packages/py-ab-testing
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test
working-directory: ./packages/py-ab-testing
env:
COVERALLS_PARALLEL: true
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: |
pip install -r requirements.txt
pip install -e .
pytest \
--cov-config=.coveragerc \
--cov=ABTesting \
--cov-branch \
--cov-report=html:cov.html \
--cov-report=term-missing:skip-covered \
tests
coveralls
finish_coveralls:
runs-on: ubuntu-latest
needs:
- js
- py
steps:
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel: true
parallel-finished: true

publish_to_prod:
if: "!contains(github.event.head_commit.author.name, 'GitHub Actions') && github.event_name == 'push' && github.ref == 'refs/heads/master'"
runs-on: ubuntu-latest
needs:
- js
- py
steps:
- uses: actions/checkout@v2
with:
token: ${{ secrets.GITHUB_ACCESS_TOKEN }}
- uses: actions/setup-node@v1
with:
node-version: 12
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: 3.8
- run: |
git config --global user.name "GitHub Actions"
git fetch --depth=1 origin $GITHUB_REF
- name: Publish to NPM
run: |
yarn install
cp npmrc-ci .npmrc
yarn lerna version --conventional-commits --yes
yarn lerna publish from-git --yes
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to Pypi
working-directory: ./packages/py-ab-testing
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python -m pip install --upgrade pip setuptools wheel twine
python setup.py sdist bdist_wheel
twine upload dist/*
16 changes: 10 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
*.log
.npmrc
.DS_Store
node_modules
coverage
packages/*/lib
*.pyc
*.log
.npmrc
.DS_Store
.idea
node_modules
coverage
packages/*/lib
*.egg-info

2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ install:
script:
- yarn lint
- yarn test
- yarn coveralls <./coverage/lcov.info
- npx coveralls <./coverage/lcov.info

jobs:
# include:
Expand Down
7 changes: 6 additions & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
"version": "1.0.5",
"npmClient": "yarn",
"useWorkspaces": true,
"ignoreChanges": ["**/__fixtures__/**", "**/__tests__/**", "**/*.md"]
"ignoreChanges": ["**/__fixtures__/**", "**/__tests__/**", "**/*.md"],
"command": {
"version": {
"allowBranch": "master"
}
}
}
2 changes: 1 addition & 1 deletion npmrc-ci
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"@types/node": "^13.7.0",
"@typescript-eslint/eslint-plugin": "^2.19.0",
"@typescript-eslint/parser": "^2.19.0",
"coveralls": "^3.0.9",
"eslint": "^6.6.0",
"eslint-config-prettier": "^6.5.0",
"eslint-formatter-friendly": "^7.0.0",
Expand Down
Binary file added packages/py-ab-testing/.coverage
Binary file not shown.
2 changes: 2 additions & 0 deletions packages/py-ab-testing/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[report]
fail_under: 100
1 change: 1 addition & 0 deletions packages/py-ab-testing/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cov.*
1 change: 1 addition & 0 deletions packages/py-ab-testing/ABTesting/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .controller import ABTestingController
44 changes: 44 additions & 0 deletions packages/py-ab-testing/ABTesting/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import Union, Dict

from crc32c import crc32


def get_modulo_value(experiment, user_id):
# type: (str, Union[str, int]) -> int
return crc32(str(user_id).encode(), crc32(experiment.encode())) % 100


def match_user_cohort(
experiment_config,
user_id,
user_profile
):
# type: (Dict, Union[str, int], Dict[str, str]) -> str
user_segment_num = get_modulo_value(experiment_config['name'], user_id)
allocated_cohort = 'control'
for cohort in experiment_config['cohorts']:
for force_include_key, force_include_val in cohort.get('force_include', {}).items():
if force_include_key in user_profile and user_profile[force_include_key] in force_include_val:
return cohort['name']
if allocated_cohort == 'control':
for allocation in cohort.get('allocation', []):
if allocation[0] <= user_segment_num < allocation[1]:
allocated_cohort = cohort['name']
break
return allocated_cohort


class ABTestingController(object):
def __init__(self, config, user_id, user_profile):
self.matched_cohorts = {
experiment_config['name']: match_user_cohort(
experiment_config,
user_id,
user_profile
)
for experiment_config in config['experiments']
}

def get_cohort(self, experiment_name):
# type: (str) -> str
return self.matched_cohorts.get(experiment_name, 'control')
22 changes: 22 additions & 0 deletions packages/py-ab-testing/ABTesting/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import hashlib
from typing import Any, Dict, Union, List


def hash_with_salt(value, salt):
# type: (Any, str) -> str
ret = hashlib.sha256()
ret.update(salt.encode())
ret.update(str(value).encode())
return ret.hexdigest()


def hash_dict(in_dict, salt):
# type: (Dict[str, Union[List[Any], Any]], str) -> Dict
return {
hash_with_salt(k, salt):
[hash_with_salt(v, salt) for v in value_list]
if isinstance(value_list, list)
else hash_with_salt(value_list, salt)
for k, value_list in in_dict.items()
if value_list
}
1 change: 1 addition & 0 deletions packages/py-ab-testing/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include package.json
Empty file.
6 changes: 6 additions & 0 deletions packages/py-ab-testing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "py-ab-testing",
"license": "MIT",
"private": true,
"version": "1.0.5"
}
4 changes: 4 additions & 0 deletions packages/py-ab-testing/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pytest>=4.6,<=4.7
pytest-cov>=2.8,<=3
coveralls>=1.11,<=2
snapshottest<1.0
34 changes: 34 additions & 0 deletions packages/py-ab-testing/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import json
from os import path

import setuptools

with open(path.join(path.dirname(__file__), "README.md"), "r") as fh:
long_description = fh.read()

with open(path.join(path.dirname(__file__), "package.json"), "r") as f:
package_conf = json.load(f)

setuptools.setup(
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/appannie/ab-testing",
packages=setuptools.find_packages(),
classifiers=[
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
install_requires=[
'crc32c>=2.0,<=3.0',
'typing'
],
**package_conf
)
Empty file.
54 changes: 54 additions & 0 deletions packages/py-ab-testing/tests/snapshots/snap_test_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
# snapshottest: v1 - https://goo.gl/zC4yUc
from __future__ import unicode_literals

from snapshottest import Snapshot


snapshots = Snapshot()

snapshots['test_match_cohort 1'] = [
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'test_allocation',
'control',
'control',
'control',
'control',
'control',
'control'
]

snapshots['test_match_cohort 2'] = [
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'control',
'test_allocation',
'control',
'test_allocation',
'control',
'test_allocation',
'control',
'control',
'control',
'control',
'control'
]
Loading

0 comments on commit 3d51b0c

Please sign in to comment.