Skip to content

Commit 56ad83e

Browse files
rhilfersashishagg
authored andcommitted
Release updates (#4)
* add tracer init to readme * added build and release features * fix makefile typo * can enable shared spans if desired
1 parent 16ae531 commit 56ad83e

File tree

10 files changed

+180
-46
lines changed

10 files changed

+180
-46
lines changed

.travis.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
sudo: required
22

33
dist: trusty
4-
54
language: python
5+
python: 3.6
66

77
services:
88
- docker
9-
10-
install:
11-
- make bootstrap
9+
10+
install: make bootstrap
11+
12+
script: make lint test integration_tests
13+
14+
before_deploy:
15+
- git stash --all
16+
- make set_version
17+
deploy:
18+
provider: pypi
19+
user: $PYPI_USERNAME
20+
password: $PYPI_PASSWORD
21+
distributions: "sdist bdist_wheel"
22+
skip_cleanup: true
23+
on:
24+
branch: master
25+
tags: true

Makefile

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,27 @@ example: bootstrap
1717
.PHONY: lint
1818
lint:
1919
pip install flake8
20-
python -m flake8 ./haystack --exclude *_pb2*
20+
python -m flake8 ./haystack --exclude *_pb2* && echo "Flake8 passed without any issues!"
2121

2222
.PHONY: integration_tests
2323
integration_tests:
2424
docker-compose -f tests/integration/docker-compose.yml -p sandbox up -d
25+
echo "sleeping for 15s while kafka/zookeeper initialize"
2526
sleep 15
26-
docker run -it
27+
docker run -it \
2728
--rm \
2829
--network=sandbox_default \
29-
-v $(pwd):/ws \
30+
-v $(PWD):/ws \
3031
-w /ws \
3132
python:3.6 \
3233
/bin/sh -c 'python setup.py install && pip install kafka-python && python tests/integration/integration.py'
3334
docker-compose -f tests/integration/docker-compose.yml -p sandbox stop
3435

35-
.PHONY: dist
36-
dist: bootstrap lint test integration_tests
37-
pip install wheel
38-
python setup.py sdist
39-
python setup.py bdist_wheel
40-
41-
.PHONY: publish
42-
publish:
43-
pip install twine
44-
python -m twine upload dist/*
36+
.PHONY: set_version
37+
set_version:
38+
pip install semver
39+
pip install requests
40+
python ./scripts/version.py
4541

4642
.PHONY: proto_compile
4743
proto_compile:

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
[![Build Status](https://travis-ci.org/ExpediaDotCom/haystack-client-python.svg?branch=master)](https://travis-ci.org/ExpediaDotCom/haystack-client-python)
2+
[![License](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/ExpediaDotCom/haystack/blob/master/LICENSE)
3+
14
# Haystack bindings for Python OpenTracing API
25
This is Haystack's client library for Python that implements [OpenTracing](https://github.com/opentracing/opentracing-python/)
36

@@ -6,11 +9,20 @@ Further information can be found on [opentracing.io](https://opentracing.io/)
69
## Using this library
710
See examples in /examples directory.
811

12+
First initialize the tracer at the application level by supplying a service name and recorder
13+
```python
14+
import opentracing
15+
from haystack import HaystackAgentRecorder
16+
from haystack import HaystackTracer
17+
18+
opentracing.tracer = HaystackTracer("a_service", HaystackAgentRecorder())
19+
```
20+
921
**If there is a Scope, it will act as the parent to any newly started Span** unless the programmer passes
1022
`ignore_active_span=True` at `start_span()/start_active_span()` time or specified parent context explicitly using
1123
`childOf=parent_context`
1224

13-
As demonstrated in the examples, starting a span can be done as a managed resource using `start_active_span()`
25+
Starting a span can be done as a managed resource using `start_active_span()`
1426
```python
1527
with opentracing.tracer.start_active_span("span-name") as scope:
1628
do_stuff()
@@ -38,4 +50,4 @@ Create a python3 virtual environment, activate it and then `make bootstrap`
3850
`make example`
3951

4052
## How to Release this library
41-
TBD
53+
Create a new release in github specifying a semver compliant tag greater than the current release version.

haystack/constants.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
# Name of the HTTP header or Key used to encode Trace ID
22
TRACE_ID_HEADER = "Trace-ID"
33

4-
# Name of the HTTP header or Key used to encode Span ID
5-
SPAN_ID_HEADER = "Span-ID"
4+
# Name of the HTTP header or Key used to encode Span ID followed by any
5+
# alternate header names to extract as Span ID
6+
SPAN_ID_HEADERS = ("Span-ID", "Message-ID",)
67

7-
# Any additional header names to decode Span ID
8-
ALTERNATE_SPAN_ID_HEADERS = ["Message-ID", ]
9-
10-
# Name of the HTTP header or Key used to encode Parent Span ID
11-
PARENT_SPAN_ID_HEADER = "Parent-ID"
8+
# Name of the HTTP header or Key used to encode Parent Span ID followed by any
9+
# alternate header names to extract as Parent Span ID
10+
PARENT_SPAN_ID_HEADERS = ("Parent-ID", "Parent-Message-ID",)
1211

1312
# Prefix of the HTTP header or Key used to encode Baggage items
1413
BAGGAGE_HEADER_PREFIX = "Baggage-"

haystack/text_propagator.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
from .span import SpanContext
33
from opentracing import SpanContextCorruptedException
44
from .constants import (
5-
SPAN_ID_HEADER,
5+
SPAN_ID_HEADERS,
66
TRACE_ID_HEADER,
7-
PARENT_SPAN_ID_HEADER,
7+
PARENT_SPAN_ID_HEADERS,
88
BAGGAGE_HEADER_PREFIX,
9-
ALTERNATE_SPAN_ID_HEADERS,
109
)
1110

1211

@@ -15,8 +14,8 @@ class TextPropagator(Propagator):
1514

1615
def inject(self, span_context, carrier):
1716
carrier[TRACE_ID_HEADER] = span_context.trace_id
18-
carrier[SPAN_ID_HEADER] = span_context.span_id
19-
carrier[PARENT_SPAN_ID_HEADER] = span_context.parent_id
17+
carrier[SPAN_ID_HEADERS[0]] = span_context.span_id
18+
carrier[PARENT_SPAN_ID_HEADERS[0]] = span_context.parent_id
2019
if span_context.baggage is not None:
2120
for item in span_context.baggage:
2221
carrier[BAGGAGE_HEADER_PREFIX + item] = \
@@ -25,24 +24,28 @@ def inject(self, span_context, carrier):
2524
def extract(self, carrier):
2625
count = 0
2726
baggage = {}
27+
parent_id = None
2828

2929
for key in carrier:
3030
value = carrier[key]
31-
if key == SPAN_ID_HEADER:
31+
if key in SPAN_ID_HEADERS:
3232
span_id = value
3333
count += 1
3434
elif key == TRACE_ID_HEADER:
3535
trace_id = value
3636
count += 1
3737
elif key.startswith(BAGGAGE_HEADER_PREFIX):
3838
baggage[key[len(BAGGAGE_HEADER_PREFIX):]] = value
39-
elif key in ALTERNATE_SPAN_ID_HEADERS:
40-
span_id = value
41-
count += 1
39+
elif key in PARENT_SPAN_ID_HEADERS:
40+
parent_id = value
4241

4342
if count == 0:
4443
return None
4544
elif count != 2:
46-
raise SpanContextCorruptedException()
45+
raise SpanContextCorruptedException(
46+
"Both SpanID and TraceID are required")
4747

48-
return SpanContext(span_id=span_id, trace_id=trace_id, baggage=baggage)
48+
return SpanContext(span_id=span_id,
49+
trace_id=trace_id,
50+
parent_id=parent_id,
51+
baggage=baggage)

haystack/tracer.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ def __init__(self,
1212
service_name,
1313
recorder,
1414
scope_manager=None,
15-
common_tags=None):
15+
common_tags=None,
16+
use_shared_spans=False):
1617
"""
1718
Initialize a Haystack Tracer instance.
1819
:param service_name: The service name to which all spans will belong.
@@ -22,6 +23,9 @@ def __init__(self,
2223
ThreadLocal scope manager.
2324
:param common_tags: An optional dictionary of tags which should be
2425
applied to all created spans for this service
26+
:param use_shared_spans: A boolean indicating whether or not to use
27+
shared spans. This is when client/server spans share the same span id.
28+
Default is to use unique span ids.
2529
"""
2630

2731
scope_manager = ThreadLocalScopeManager() if scope_manager is None \
@@ -31,6 +35,7 @@ def __init__(self,
3135
self._common_tags = {} if common_tags is None else common_tags
3236
self.service_name = service_name
3337
self.recorder = recorder
38+
self.use_shared_spans = use_shared_spans
3439
self.register_propagator(Format.TEXT_MAP, TextPropagator())
3540
self.register_propagator(Format.HTTP_HEADERS, TextPropagator())
3641

@@ -87,10 +92,14 @@ def start_span(self,
8792

8893
new_ctx = SpanContext(span_id=format(uuid.uuid4()))
8994
if parent_ctx is not None:
95+
new_ctx.trace_id = parent_ctx.trace_id
9096
if parent_ctx.baggage is not None:
9197
new_ctx._baggage = parent_ctx.baggage.copy()
92-
new_ctx.trace_id = parent_ctx.trace_id
93-
new_ctx.parent_id = parent_ctx.span_id
98+
if self.use_shared_spans:
99+
new_ctx.span_id = parent_ctx.span_id
100+
new_ctx.parent_id = parent_ctx.parent_id
101+
else:
102+
new_ctx.parent_id = parent_ctx.span_id
94103
else:
95104
new_ctx.trace_id = format(uuid.uuid4())
96105

scripts/version.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import requests
2+
import json
3+
import semver
4+
import os
5+
6+
7+
VERSION_PLACEHOLDER = "X.X.X"
8+
9+
10+
def update_setup(new_version):
11+
with open("setup.py") as f:
12+
content = f.read()
13+
if VERSION_PLACEHOLDER not in content:
14+
raise Exception("setup.py version parameter has been modified")
15+
16+
with open("setup.py", "w") as f:
17+
print("changing setup.py version parameter")
18+
content = content.replace(VERSION_PLACEHOLDER, new_version)
19+
f.write(content)
20+
21+
22+
def pypi_version(response):
23+
data = json.loads(response.text)
24+
if "info" in data:
25+
return data["info"]["version"]
26+
else:
27+
raise Exception(f"Invalid response {data} from pypi. "
28+
f"info tag missing")
29+
30+
31+
if __name__ == "__main__":
32+
tag_ver = os.getenv("TRAVIS_TAG")
33+
34+
if tag_ver is None:
35+
raise TypeError("Expected TRAVIS_TAG environment variable to be set")
36+
37+
# verify tag is valid semver
38+
semver.parse(tag_ver)
39+
40+
response = requests.get("https://pypi.org/pypi/haystack-client/json")
41+
42+
if response.ok:
43+
old_ver = pypi_version(response)
44+
elif response.status_code == 404:
45+
print("must be a new repo")
46+
old_ver = "0.0.0"
47+
else:
48+
raise Exception(f"Failed to get existing version information from pypi."
49+
f" Response was {response}")
50+
51+
if semver.compare(tag_ver, old_ver) > 0:
52+
print(f"Previouis PyPI version is {old_ver}, updating setup.py with new"
53+
f" version {tag_ver}")
54+
update_setup(tag_ver)
55+
else:
56+
raise Exception(f"pypi version {old_ver} is newer than tagged version"
57+
f" {tag_ver}")

setup.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
from setuptools import setup, find_packages
22

3+
with open("README.md") as file:
4+
long_description = file.read()
5+
36
setup(
47
name="haystack-client",
5-
version="1.0.0",
8+
version="X.X.X", # do not change, makefile updates this prior to a release
9+
url="https://github.com/ExpediaDotCom/haystack-client-python",
610
description="Haystack Python OpenTracing Implementation",
711
author="Ryan Hilfers, Haystack",
812
author_email="[email protected]",
13+
long_description=long_description,
14+
long_description_content_type="text/markdown",
915
install_requires=["opentracing==2.0.0",
1016
"requests>=2.19,<3.0",
1117
"requests-futures==0.9.9,<1.0",
@@ -24,6 +30,6 @@
2430
"License :: OSI Approved :: Apache Software License",
2531
],
2632
python_requires=">=3.5",
27-
keywords=['opentracing', 'haystack', 'tracing', 'microservices', 'distributed'],
33+
keywords=["opentracing", "haystack", "tracing", "microservices", "distributed"],
2834
packages=find_packages()
2935
)

tests/unit/test_text_propagator.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,21 @@ def setUp(self):
1010

1111
def test_message_id_in_carrier_should_be_extracted_as_span_id(self):
1212
span_id = "1234"
13-
carrier = {"Message-ID": span_id, "Trace-ID": "123456"}
13+
parent_id = "4321"
14+
trace_id = "1212"
15+
carrier = {"Message-ID": span_id, "Parent-Message-ID": parent_id,
16+
"Trace-ID": trace_id}
1417

1518
ctx = self.propagator.extract(carrier)
1619

1720
self.assertEqual(ctx.span_id, span_id)
21+
self.assertEqual(ctx.parent_id, parent_id)
22+
self.assertEqual(ctx.trace_id, trace_id)
1823

1924
def test_corrupted_context_throws_exception(self):
2025
span_id = "1234"
21-
carrier = {"Message-ID": span_id, "Span-ID": "123456", "Trace-ID": "1234567"} # two span id's are invalid
26+
carrier = {"Message-ID": span_id, "Span-ID": "123456",
27+
"Trace-ID": "1234567"} # two span id's are invalid
2228

2329
self.assertRaises(SpanContextCorruptedException, self.propagator.extract, carrier)
2430

0 commit comments

Comments
 (0)