Skip to content

Commit aef1276

Browse files
authored
Merge pull request #93 from aaxelb/9697-granularity
[ENG-9697] index granularity
2 parents ee73c98 + 20b7080 commit aef1276

File tree

20 files changed

+512
-287
lines changed

20 files changed

+512
-287
lines changed

CONTRIBUTING.md

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,70 @@
11
# Contributing
2-
(TODO: update)
2+
(TODO: expand)
33

4-
## Code conventions
4+
## code conventions
55

6+
### naming
67

7-
## Setting up local devloop
8+
### docstrings
89

9-
### 0. get the code
10+
### type annotations
11+
12+
### ...
13+
14+
## setting up local devloop
15+
16+
### get a copy of the code
1017
for example:
1118
```
1219
git clone https://github.com/CenterForOpenScience/django-elasticsearch-metrics.git djelme
1320
cd djelme
1421
```
1522

16-
### 1. put environment together
23+
### be in a fitting environment
1724
create and activate a [python virtual environment](https://docs.python.org/3.14/tutorial/venv.html)
18-
with python version 3.10+ -- there are many tools for this; you may already have your own way.
25+
with python version 3.10+ and [`poetry` for python package management](https://python-poetry.org) -- there are many ways to do this (including with `poetry` itself); you may already have your own way.
26+
27+
(if you'd rather work with containers and `docker-compose`-likes, see `using docker-style container tools`, below)
1928

2029
here's an example using `venv` (in python's standard lib) to create the virtual environment in a `.venv` directory, with bash:
2130
```
2231
python -m venv .venv && source .venv/bin/activate
2332
```
2433

25-
install dependencies (listed in `pyproject.toml`), including the `dev` extra (and maybe `anydjango`) -- here's an example using `pip`:
34+
install [poetry](https://python-poetry.org):
2635
```
27-
pip install -e '.[dev,anydjango]'
36+
pip install poetry
2837
```
2938

30-
### Run tests and checks
39+
install the current project (in editable mode) and dependencies (with the `dev` dependency group and the extras `elastic6`, `elastic8`, `anydjango`):
40+
```
41+
poetry install --with=dev --extras=elastic6 --extras=elastic8 --extras=anydjango
42+
```
43+
44+
### run tests and checks
3145

3246
these expect elasticsearches to be running and configured in `elasticsearch_metrics/tests/settings.py` (or set environment variables `ELASTICSEARCH6_URL` and `ELASTICSEARCH8_URL`) -- see `using docker-style container tools`, below, for one way to do that
3347

3448
running the python module `elasticsearch_metrics.tests` will run tests and linting checks
35-
-- should always pass before merging to `main`
49+
-- any code merged to `main` should pass for all supported python and django combinations
3650

3751
```
3852
python -m elasticsearch_metrics.tests
3953
```
54+
without args, equivalent to `--test --lint`
55+
4056
optional args:
41-
- `--devloop`: adds `--failfast --pdb` to test args
42-
- `--coverage`: print code coverage report
43-
- `--test`: run only tests (not linting)
44-
- `--lint`: run only linting checks (not tests)
57+
- `--test`: run tests and display code coverage report
58+
- `--lint`: run linting checks
59+
- `--autofix`: autofix linting errors; autoformat code
60+
- `--no-coverage`: skip gathering and reporting code coverage
61+
62+
any additional args are passed thru to [django-admin test](https://docs.djangoproject.com/en/5.2/ref/django-admin/#test)
4563

64+
example devloop:
65+
```
66+
python -m elasticsearch_metrics.tests --failfast --pdb
67+
```
4668
see `elasticsearch_metrics/tests/__main__.py` for more details
4769

4870
### Run the shell with:
@@ -67,7 +89,7 @@ tox
6789
```
6890

6991
### (optional) using docker-style container tools
70-
see `testbox.Containerfile` and `docker-compose.yml` -- running `testbox` runs `python -m elasticsearch_metrics.tests`
92+
see `testbox.Containerfile` and `docker-compose.yml` for a local setup -- running `testbox` runs `python -m elasticsearch_metrics.tests`
7193

7294
(note: examples use `pc` aliased to a `docker-compose` equivalent)
7395

@@ -81,7 +103,7 @@ run tests and lint
81103
pc up testbox
82104
```
83105

84-
example devloop -- use current code, debugger on error, stop on failure
106+
example devloop -- build with current code, lint and run tests with debugger on error, stop on failure
85107
```
86108
pc run --build --rm --no-deps testbox poetry run python -m elasticsearch_metrics.tests --devloop
87109
```

README.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ python importables:
1414

1515
* Python >=3.10
1616
* Django 4.2, 5.1, or 5.2
17-
* Elasticsearch 6 or 8
17+
* Elasticsearch 8 (or 6, for deprecated back-compat)
1818

1919
## Install
2020

@@ -69,7 +69,6 @@ DJELME_AUTOSETUP = True
6969

7070
...or be sure to run the `djelme_backend_setup` management command before trying to store anything.
7171
```shell
72-
# This will
7372
# This will create an index template for usagerecord timeseries indexes
7473
python manage.py djelme_backend_setup
7574
```
@@ -94,8 +93,8 @@ UsageRecord.search()
9493

9594
## Timeseries indexes
9695

97-
By default, behind the scenes, a new index is created for each record type for each month
98-
in which a record is saved (using UTC timezone). You can change the per-index timespan by
96+
By default, behind the scenes, a new elasticsearch index is created for each record type for each month
97+
in which a record is saved (using UTC timezone). You can set a default change the per-index timespan by
9998
setting `Meta.timedepth` on the record type.
10099

101100
- index per day, '...YYYY_MM_DD...': `timedepth = 3`
@@ -185,7 +184,7 @@ def test_something():
185184

186185
## Configuration
187186

188-
* `DJELME_TIMESERIES_BACKENDS`: Named backends for storing or searching records from your django app
187+
* `DJELME_BACKENDS`: Named backends for storing or searching records from your django app
189188
-- nested mapping from backend name (any string, your choice) to python-importable paths
190189
for modules that (like `"elasticsearch_metrics.imps.elastic8"`)
191190
to "imp kwargs" config dictionaries given to the imp module's `djelme_backend` constructor
@@ -202,12 +201,18 @@ def test_something():
202201
}
203202
```
204203

205-
* `DJELME_AUTOSETUP`: Optional feature, default `False` -- set `True` for backend setup
206-
(like creating index templates in elasticsearch) to run automatically when your django app starts.
207-
208-
* `ELASTICSEARCH_METRICS_DATE_FORMAT`: Date format to use when creating
209-
indexes. Default: `%Y.%m.%d` (same date format Elasticsearch uses for
210-
[date math](https://www.elastic.co/guide/en/elasticsearch/reference/current/date-math-index-names.html))
204+
* `DJELME_AUTOSETUP`: Optional feature, default `False` --
205+
set `True` to run backend setup automatically when your django app starts
206+
(like creating index templates in elasticsearch, if they don't already exist)
207+
208+
* `DJELME_DEFAULT_TIMEDEPTH`: Set the granularity of timeseries indexes by the number of "time parts" in index names
209+
```
210+
DJELME_DEFAULT_TIMEDEPTH = 1 # yearly indexes; YYYY
211+
DJELME_DEFAULT_TIMEDEPTH = 2 # monthly indexes; YYYY_MM
212+
DJELME_DEFAULT_TIMEDEPTH = 3 # daily indexes; YYYY_MM_DD (this is the default)
213+
DJELME_DEFAULT_TIMEDEPTH = 4 # hourly indexes; YYYY_MM_DD_HH
214+
```
215+
you can also set `Meta.timedepth` on a specific record type; this will take precedence
211216
212217
## Management commands
213218

elasticsearch_metrics/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
1-
__version__ = "2022.0.6"
1+
__all__ = (
2+
"apps",
3+
"exceptions",
4+
"protocols",
5+
"registry",
6+
"signals",
7+
"imps",
8+
"management",
9+
"util",
10+
)

elasticsearch_metrics/factory.py

Lines changed: 0 additions & 21 deletions
This file was deleted.

elasticsearch_metrics/imps/elastic6.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
consider this code frozen/deprecated -- will be removed once no longer needed
44
"""
55

6+
from __future__ import annotations
67
import collections
78
from collections.abc import Iterator
89
import dataclasses
@@ -13,11 +14,13 @@
1314
from django.conf import settings
1415
from django.utils import timezone
1516
from elasticsearch6.exceptions import NotFoundError
16-
import elasticsearch6_dsl
1717
from elasticsearch6_dsl import Document, connections, Date
1818
from elasticsearch6_dsl.document import IndexMeta, MetaField
1919
from elasticsearch6_dsl.index import Index
2020

21+
# re-export all fields, for back-compat convenience
22+
from elasticsearch6_dsl.field import * # noqa: F40
23+
2124
from elasticsearch_metrics import signals
2225
from elasticsearch_metrics import exceptions
2326
from elasticsearch_metrics.protocols import ProtoDjelmeBackend
@@ -27,8 +30,6 @@
2730

2831
logger = logging.getLogger(__name__)
2932

30-
fields = elasticsearch6_dsl.field
31-
3233

3334
class ReadonlyAttrMap:
3435
def __init__(self, inner_obj):
@@ -49,7 +50,12 @@ def __setitem__(self, key, value):
4950

5051
def _get_default_using():
5152
"""get the elasticsearch-dsl connection name to use"""
52-
(_backend_name,) = djelme_registry.each_backend_name(imp_module_name=__name__)
53+
_available_backends = djelme_registry.each_backend_name(imp_module_name=__name__)
54+
try:
55+
(_backend_name,) = _available_backends
56+
except ValueError:
57+
logger.warning(f"no djelme backends configured using imp module {__name__!r}!")
58+
return None
5359
return _backend_name
5460

5561

@@ -294,6 +300,10 @@ def save(self, using=None, index=None, validate=True, **kwargs):
294300
signals.post_save.send(cls, instance=self, using=using, index=index)
295301
return ret
296302

303+
def djelme_index_name(self) -> str: # for ProtoDjelmeRecord
304+
assert self.timestamp is not None
305+
return self.get_index_name(self.timestamp)
306+
297307
@classmethod
298308
def _default_index(cls, index=None):
299309
"""Overrides Document._default_index so that .search, .get, etc.
@@ -332,6 +342,12 @@ def elastic6_client(self):
332342
# assumes `connections.configure` was already called
333343
return connections.get_connection(self.backend_name)
334344

345+
def djelme_backend_name(self) -> str: # for ProtoDjelmeBackend
346+
return self.backend_name
347+
348+
def djelme_imp_kwargs(self) -> dict[str, str]: # for ProtoDjelmeBackend
349+
return self.imp_kwargs
350+
335351
def djelme_setup(self, recordtypes: collections.abc.Iterable[type]) -> None:
336352
for _metric_type in recordtypes:
337353
assert issubclass(_metric_type, Metric)
@@ -358,5 +374,8 @@ def djelme_when_ready( # for ProtoDjelmeImp
358374
backends: Iterator[ProtoDjelmeBackend],
359375
) -> None:
360376
connections.configure(
361-
**{_backend.backend_name: _backend.imp_kwargs for _backend in backends}
377+
**{
378+
_backend.djelme_backend_name(): _backend.djelme_imp_kwargs()
379+
for _backend in backends
380+
}
362381
)

0 commit comments

Comments
 (0)