Skip to content

Commit f0dbdc9

Browse files
authored
Release 0.39.1
See release notes.
2 parents aee1b09 + a10edad commit f0dbdc9

File tree

14 files changed

+480
-83
lines changed

14 files changed

+480
-83
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 0.39.1 - 2024-08-13
4+
5+
#### Bug fixes
6+
- Fixed an issue where a symbol list which contained a `None` would produce a convoluted exception
7+
38
## 0.39.0 - 2024-07-30
49

510
#### Enhancements

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The library is fully compatible with the latest distribution of Anaconda 3.8 and
3232
The minimum dependencies as found in the `pyproject.toml` are also listed below:
3333
- python = "^3.8"
3434
- aiohttp = "^3.8.3"
35-
- databento-dbn = "0.19.1"
35+
- databento-dbn = "0.20.0"
3636
- numpy= ">=1.23.5"
3737
- pandas = ">=1.5.3"
3838
- pip-system-certs = ">=4.0" (Windows only)

databento/common/parsing.py

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -58,60 +58,90 @@ def optional_values_list_to_string(
5858
return values_list_to_string(values)
5959

6060

61-
@singledispatch
61+
def optional_string_to_list(
62+
value: Iterable[str] | str | None,
63+
) -> Iterable[str] | list[str] | None:
64+
"""
65+
Convert a comma-separated string into a list of strings, or return the
66+
original input if not a string.
67+
68+
Parameters
69+
----------
70+
value : iterable of str or str, optional
71+
The input value to be parsed.
72+
73+
Returns
74+
-------
75+
Iterable[str] | list[str] | `None`
76+
77+
"""
78+
return value.strip().strip(",").split(",") if isinstance(value, str) else value
79+
80+
6281
def optional_symbols_list_to_list(
6382
symbols: Iterable[str | int | Integral] | str | int | Integral | None,
6483
stype_in: SType,
6584
) -> list[str]:
6685
"""
67-
Create a list from a symbols string or iterable of symbol strings (if not
68-
None).
86+
Create a list from an optional symbols string or iterable of symbol
87+
strings. If symbols is `None`, this function returns `[ALL_SYMBOLS]`.
6988
7089
Parameters
7190
----------
7291
symbols : Iterable of str or int or Number, or str or int or Number, optional
73-
The symbols to concatenate.
92+
The symbols to concatenate; or `None`.
7493
stype_in : SType
7594
The input symbology type for the request.
7695
7796
Returns
7897
-------
7998
list[str]
8099
81-
Notes
82-
-----
83-
If None is given, [ALL_SYMBOLS] is returned.
100+
See Also
101+
--------
102+
symbols_list_to_list
84103
85104
"""
86-
raise TypeError(
87-
f"`{symbols}` is not a valid type for symbol input; "
88-
"allowed types are Iterable[str | int], str, int, and None.",
89-
)
105+
if symbols is None:
106+
return [ALL_SYMBOLS]
107+
return symbols_list_to_list(symbols, stype_in)
90108

91109

92-
@optional_symbols_list_to_list.register(cls=type(None))
93-
def _(_: None, __: SType) -> list[str]:
110+
@singledispatch
111+
def symbols_list_to_list(
112+
symbols: Iterable[str | int | Integral] | str | int | Integral,
113+
stype_in: SType,
114+
) -> list[str]:
94115
"""
95-
Dispatch method for optional_symbols_list_to_list. Handles None which
96-
defaults to [ALL_SYMBOLS].
116+
Create a list from a symbols string or iterable of symbol strings.
97117
98-
See Also
99-
--------
100-
optional_symbols_list_to_list
118+
Parameters
119+
----------
120+
symbols : Iterable of str or int or Number, or str or int or Number
121+
The symbols to concatenate.
122+
stype_in : SType
123+
The input symbology type for the request.
124+
125+
Returns
126+
-------
127+
list[str]
101128
102129
"""
103-
return [ALL_SYMBOLS]
130+
raise TypeError(
131+
f"`{symbols}` is not a valid type for symbol input; "
132+
"allowed types are Iterable[str | int], str, and int.",
133+
)
104134

105135

106-
@optional_symbols_list_to_list.register(cls=Integral)
136+
@symbols_list_to_list.register(cls=Integral)
107137
def _(symbols: Integral, stype_in: SType) -> list[str]:
108138
"""
109139
Dispatch method for optional_symbols_list_to_list. Handles integral types,
110140
alerting when an integer is given for STypes that expect strings.
111141
112142
See Also
113143
--------
114-
optional_symbols_list_to_list
144+
symbols_list_to_list
115145
116146
"""
117147
if stype_in == SType.INSTRUMENT_ID:
@@ -122,15 +152,15 @@ def _(symbols: Integral, stype_in: SType) -> list[str]:
122152
)
123153

124154

125-
@optional_symbols_list_to_list.register(cls=str)
155+
@symbols_list_to_list.register(cls=str)
126156
def _(symbols: str, stype_in: SType) -> list[str]:
127157
"""
128158
Dispatch method for optional_symbols_list_to_list. Handles str, splitting
129159
on commas and validating smart symbology.
130160
131161
See Also
132162
--------
133-
optional_symbols_list_to_list
163+
symbols_list_to_list
134164
135165
"""
136166
if not symbols:
@@ -147,19 +177,19 @@ def _(symbols: str, stype_in: SType) -> list[str]:
147177
return list(map(str.upper, map(str.strip, symbol_list)))
148178

149179

150-
@optional_symbols_list_to_list.register(cls=Iterable)
180+
@symbols_list_to_list.register(cls=Iterable)
151181
def _(symbols: Iterable[Any], stype_in: SType) -> list[str]:
152182
"""
153183
Dispatch method for optional_symbols_list_to_list. Handles Iterables by
154184
dispatching the individual members.
155185
156186
See Also
157187
--------
158-
optional_symbols_list_to_list
188+
symbols_list_to_list
159189
160190
"""
161191
symbol_to_list = partial(
162-
optional_symbols_list_to_list,
192+
symbols_list_to_list,
163193
stype_in=stype_in,
164194
)
165195
aggregated: list[str] = []

databento/historical/api/batch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
from databento.common.http import check_http_error
3737
from databento.common.parsing import datetime_to_string
3838
from databento.common.parsing import optional_datetime_to_string
39-
from databento.common.parsing import optional_symbols_list_to_list
4039
from databento.common.parsing import optional_values_list_to_string
40+
from databento.common.parsing import symbols_list_to_list
4141
from databento.common.publishers import Dataset
4242
from databento.common.types import Default
4343
from databento.common.validation import validate_enum
@@ -147,7 +147,7 @@ def submit_job(
147147
148148
"""
149149
stype_in_valid = validate_enum(stype_in, SType, "stype_in")
150-
symbols_list = optional_symbols_list_to_list(symbols, stype_in_valid)
150+
symbols_list = symbols_list_to_list(symbols, stype_in_valid)
151151
data: dict[str, object | None] = {
152152
"dataset": validate_semantic_string(dataset, "dataset"),
153153
"start": datetime_to_string(start),

databento/live/protocol.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from databento.common.error import BentoError
1818
from databento.common.iterator import chunk
1919
from databento.common.parsing import optional_datetime_to_unix_nanoseconds
20-
from databento.common.parsing import optional_symbols_list_to_list
20+
from databento.common.parsing import symbols_list_to_list
2121
from databento.common.publishers import Dataset
2222
from databento.common.types import DBNRecord
2323
from databento.common.validation import validate_enum
@@ -310,7 +310,7 @@ def subscribe(
310310
)
311311

312312
stype_in_valid = validate_enum(stype_in, SType, "stype_in")
313-
symbols_list = optional_symbols_list_to_list(symbols, stype_in_valid)
313+
symbols_list = symbols_list_to_list(symbols, stype_in_valid)
314314

315315
subscriptions: list[SubscriptionRequest] = []
316316
for batch in chunk(symbols_list, SYMBOL_LIST_BATCH_SIZE):

databento/reference/api/corporate.py

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
from databento.common.http import BentoHttpAPI
1414
from databento.common.parsing import convert_date_columns
1515
from databento.common.parsing import convert_datetime_columns
16-
from databento.common.parsing import datetime_to_date_string
17-
from databento.common.parsing import optional_date_to_string
16+
from databento.common.parsing import datetime_to_string
17+
from databento.common.parsing import optional_datetime_to_string
18+
from databento.common.parsing import optional_string_to_list
1819
from databento.common.parsing import optional_symbols_list_to_list
19-
from databento.common.publishers import Dataset
20-
from databento.common.validation import validate_semantic_string
2120

2221

2322
class CorporateActionsHttpAPI(BentoHttpAPI):
@@ -31,12 +30,16 @@ def __init__(self, key: str, gateway: str) -> None:
3130

3231
def get_range(
3332
self,
34-
start_date: date | str,
35-
end_date: date | str | None = None,
36-
dataset: Dataset | str | None = None,
33+
start: pd.Timestamp | date | str | int,
34+
end: pd.Timestamp | date | str | int | None = None,
35+
index: str = "event_date",
3736
symbols: Iterable[str] | str | None = None,
3837
stype_in: SType | str = "raw_symbol",
3938
events: Iterable[str] | str | None = None,
39+
countries: Iterable[str] | str | None = None,
40+
security_types: Iterable[str] | str | None = None,
41+
flatten: bool = True,
42+
pit: bool = False,
4043
) -> pd.DataFrame:
4144
"""
4245
Request a new corporate actions time series from Databento.
@@ -45,12 +48,17 @@ def get_range(
4548
4649
Parameters
4750
----------
48-
start_date : date or str
49-
The start date (UTC) of the request time range (inclusive).
50-
end_date : date or str, optional
51-
The end date (UTC) of the request time range (exclusive).
52-
dataset : Dataset or str, optional
53-
The dataset code (string identifier) for the request.
51+
start : pd.Timestamp or date or str or int
52+
The start datetime of the request time range (inclusive).
53+
Assumes UTC as timezone unless passed a tz-aware object.
54+
If an integer is passed, then this represents nanoseconds since the UNIX epoch.
55+
end : pd.Timestamp or date or str or int, optional
56+
The end datetime of the request time range (exclusive).
57+
Assumes UTC as timezone unless passed a tz-aware object.
58+
If an integer is passed, then this represents nanoseconds since the UNIX epoch.
59+
index : str, default 'event_date'
60+
The index column to filter the `start` and `end` time range on.
61+
Use any of 'event_date', 'ex_date' or 'ts_record'.
5462
symbols : Iterable[str] or str, optional
5563
The symbols to filter for. Takes up to 2,000 symbols per request.
5664
If more than 1 symbol is specified, the data is merged and sorted by time.
@@ -62,28 +70,48 @@ def get_range(
6270
events : Iterable[str] or str, optional
6371
The event types to filter for.
6472
Takes any number of event types per request.
65-
If not specified then will be for **all** event types.
73+
If not specified then will select **all** event types by default.
6674
See [EVENT](https://databento.com/docs/standards-and-conventions/reference-data-enums#event) enum.
75+
countries : Iterable[str] or str, optional
76+
The listing countries to filter for.
77+
Takes any number of two letter ISO 3166-1 alpha-2 country codes per request.
78+
If not specified then will select **all** listing countries by default.
79+
See [CNTRY](https://databento.com/docs/standards-and-conventions/reference-data-enums#cntry) enum.
80+
security_types : Iterable[str] or str, optional
81+
The security types to filter for.
82+
Takes any number of security types per request.
83+
If not specified then will select **all** security types by default.
84+
See [SECTYPE](https://databento.com/docs/standards-and-conventions/reference-data-enums#sectype) enum.
85+
flatten : bool, default True
86+
If nested JSON objects within the `date_info`, `rate_info`, and `event_info` fields
87+
should be flattened into separate columns in the resulting DataFrame.
88+
pit : bool, default False
89+
Determines whether to retain all historical records or only the latest records.
90+
If True, all historical records for each `event_unique_id` will be retained, preserving
91+
the complete point-in-time history.
92+
If False (default), the DataFrame will include only the most recent record for each
93+
`event_unique_id` based on the `ts_record` timestamp.
6794
6895
Returns
6996
-------
7097
pandas.DataFrame
7198
The data converted into a data frame.
7299
73100
"""
74-
dataset = validate_semantic_string(dataset, "dataset") if dataset is not None else None
75101
symbols_list = optional_symbols_list_to_list(symbols, SType.RAW_SYMBOL)
76-
77-
if isinstance(events, str):
78-
events = events.strip().strip(",").split(",")
102+
events = optional_string_to_list(events)
103+
countries = optional_string_to_list(countries)
104+
security_types = optional_string_to_list(security_types)
79105

80106
data: dict[str, object | None] = {
81-
"start_date": datetime_to_date_string(start_date),
82-
"end_date": optional_date_to_string(end_date),
83-
"dataset": dataset,
107+
"start": datetime_to_string(start),
108+
"end": optional_datetime_to_string(end),
109+
"index": index,
84110
"symbols": ",".join(symbols_list),
85111
"stype_in": stype_in,
86112
"events": ",".join(events) if events else None,
113+
"countries": ",".join(countries) if countries else None,
114+
"security_types": ",".join(security_types) if security_types else None,
87115
}
88116

89117
response = self._post(
@@ -93,7 +121,35 @@ def get_range(
93121
)
94122

95123
df = pd.read_json(StringIO(response.text), lines=True)
124+
if df.empty:
125+
return df
126+
96127
convert_datetime_columns(df, CORPORATE_ACTIONS_DATETIME_COLUMNS)
97128
convert_date_columns(df, CORPORATE_ACTIONS_DATE_COLUMNS)
98129

130+
if flatten:
131+
# Normalize the dynamic JSON fields
132+
date_info_normalized = pd.json_normalize(df["date_info"]).set_index(df.index)
133+
rate_info_normalized = pd.json_normalize(df["rate_info"]).set_index(df.index)
134+
event_info_normalized = pd.json_normalize(df["event_info"]).set_index(df.index)
135+
136+
# Merge normalized columns
137+
df = df.merge(date_info_normalized, left_index=True, right_index=True)
138+
df = df.merge(rate_info_normalized, left_index=True, right_index=True)
139+
df = df.merge(event_info_normalized, left_index=True, right_index=True)
140+
141+
# Drop the original JSON columns
142+
df.drop(columns=["date_info", "rate_info", "event_info"], inplace=True)
143+
144+
if pit:
145+
df.set_index(index, inplace=True)
146+
df.sort_index(inplace=True)
147+
else:
148+
# Filter for the latest record of each unique event
149+
df.sort_values("ts_record", inplace=True)
150+
df = df.groupby("event_unique_id").agg("last").reset_index()
151+
df.set_index(index, inplace=True)
152+
if index != "ts_record":
153+
df.sort_index(inplace=True)
154+
99155
return df

databento/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.39.0"
1+
__version__ = "0.39.1"

0 commit comments

Comments
 (0)