diff --git a/.circleci/config.yml b/.circleci/config.yml index 0a9101cb6e7f1f175c74779ce4451c00ca5320bb..4fc7413ca8b117b30be75d7fb1c857f4a69c1b43 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,7 +17,7 @@ jobs: name: Install necessary modules - run: command: | - sudo python setup.py test + sudo coverage run setup.py test name: Test workflows: diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000000000000000000000000000000000..001df73cf25030d81c4bc49cc70c5ae66bd7f27f --- /dev/null +++ b/.coveragerc @@ -0,0 +1,15 @@ +[run] +branch=1 +plugins = Cython.Coverage +source= + tempsdb +concurrency=thread +omit= + tests/* + .eggs/* + setup.py + tempsdb/__init__.py + +[report] +include= + tempsdb/* diff --git a/.gitignore b/.gitignore index e51a1344b61ddd49a377c83403342bcd2fb2adbe..791fd86278a597bc579fdb39e6b1bcaf03da2cc5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ __pycache__/ # C extensions *.so - +tempsdb/__bootstrap__.pyx # Distribution / packaging .Python build/ diff --git a/README.md b/README.md index c1f28aa6cffa354a6bedf243349c35ed6b77f062..4cbef08d955da19e6100f3d4f350ef63465f1ac8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ So no variable encoding for you! ## v0.3 -* _TBA_ +* added `TimeSeries.get_current_value` ## v0.2 diff --git a/setup.py b/setup.py index 2014defde6f4daf237dffca23f4870377bba8621..6a6ae854e55122e950ff2e9fe7924b59b6048216 100644 --- a/setup.py +++ b/setup.py @@ -19,15 +19,16 @@ def find_pyx(*path) -> tp.List[str]: # Extension('tempsdb.series', ['tempsdb/series.pyx']), # Extension('tempsdb.iterators', ['tempsdb/iterators.pyx'])] # +directives = {'language_level': '3'} +if 'CI' in os.environ: + directives.update(profile=True, linetrace=True) setup(name='tempsdb', version='0.3_a1', packages=['tempsdb'], install_requires=['satella>=2.14.21', 'ujson'], ext_modules=build([Multibuild('tempsdb', find_pyx('tempsdb')), ], - compiler_directives={ - 'language_level': '3', - }), + compiler_directives=directives), # ext_modules=cythonize(extensions, # gdb_debug=True, # compiler_directives={ diff --git a/tempsdb/iterators.pyx b/tempsdb/iterators.pyx index d94d6703ef5e95b5d14e06e456a12a20f3ea63b9..9a0621b7dbeabf77540564f1f5ec315be836278c 100644 --- a/tempsdb/iterators.pyx +++ b/tempsdb/iterators.pyx @@ -8,6 +8,14 @@ cdef class Iterator: """ Iterator that allows iterating through result. + Can be used as a context manager: + + >>> with series.iterate_range(0, 5000) as it: + >>> for timestamp, value in it: + >>> ... + + It will close itself automatically. + At most basic this implements an iterator interface, iterating over tp.Tuple[int, bytes] - timestamp and data @@ -25,6 +33,12 @@ cdef class Iterator: self.is_first = False self.is_last = False + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + def __del__(self): self.close() diff --git a/tempsdb/series.pxd b/tempsdb/series.pxd index 57eda5d77c5bac55eebcd4482f6886be03cf825c..fb80b3eee9d2a48b0a4a84178fc45e26451d0bd7 100644 --- a/tempsdb/series.pxd +++ b/tempsdb/series.pxd @@ -37,6 +37,7 @@ cdef class TimeSeries: cdef unsigned int get_index_of_chunk_for(self, unsigned long long timestamp) cpdef int trim(self, unsigned long long timestamp) except -1 cpdef unsigned long open_chunks_ram_size(self) + cpdef tuple get_current_value(self) cdef inline int get_references_for(self, unsigned long long timestamp): return self.refs_chunks.get(timestamp, 0) diff --git a/tempsdb/series.pyx b/tempsdb/series.pyx index 3bcff38da47c8d2b758cae233a77512dd8bfa2c9..fe6e7f78b93cb6604ce95bf7605e635e9aa6f603 100644 --- a/tempsdb/series.pyx +++ b/tempsdb/series.pyx @@ -23,6 +23,26 @@ cdef class TimeSeries: :ivar name: name of the series (str) """ + cpdef tuple get_current_value(self): + """ + Return latest value of this series + + .. versionadded:: 0.3 + + :return: tuple of (timestamp, value) + :rtype: tp.Tuple[int, bytes] + :raises ValueError: series has no data + """ + if self.last_chunk is None: + raise ValueError('No data in series') + cdef: + Iterator it = self.iterate_range(self.last_chunk.max_ts, self.last_chunk.max_ts) + tuple tpl = it.next() + try: + return tpl + finally: + it.close() + def __init__(self, path: str, name: str, use_descriptor_based_access: bool = False): self.descriptor_based_access = use_descriptor_based_access self.mpm = None diff --git a/tests/test_db.py b/tests/test_db.py index 29f19e95893654d31fa4a61e1e2ecf696c8bcd78..4e24666d12c8a0ba23684576d56f4a196b37f7a4 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -10,6 +10,14 @@ class TestDB(unittest.TestCase): series = create_series('test3', 'test3', 10, 4096) for i in range(8000): series.append(i, b'\x00'*10) + + self.assertEqual(series.get_current_value(), (i, b'\x00'*10)) + + with series.iterate_range(i, i) as it: + lst = list(it) + self.assertEqual(len(lst), 1) + self.assertEqual(lst[0][0], i) + series.trim(4100) self.assertEqual(len(os.listdir('test3')), 2) diff --git a/unittest.Dockerfile b/unittest.Dockerfile index 8d69b7f99631c00525297b570b1194e13c8c0b57..c567defffd37fcb7ccd468a3e142a8ef7aca71dd 100644 --- a/unittest.Dockerfile +++ b/unittest.Dockerfile @@ -1,13 +1,16 @@ FROM python:3.8 -RUN pip install satella snakehouse nose2 wheel ujson +RUN pip install satella snakehouse nose2 wheel ujson coverage ADD tempsdb /app/tempsdb ADD setup.py /app/setup.py +ADD .coveragerc /app/.coveragerc ADD setup.cfg /app/setup.cfg WORKDIR /app + +ENV CI=true RUN python setup.py build_ext --inplace ADD tests /app/tests -CMD ["nose2", "-vv"] +CMD ["coverage", "run", "-m", "nose2", "-vv"]