Skip to content

Commit dbdbaa3

Browse files
committed
Merge branch 'feat-device-reader-constructors' of https://github.com/harp-tech/harp-python into feat-device-reader-constructors
2 parents a76c6f0 + 24b8977 commit dbdbaa3

File tree

2 files changed

+248
-1
lines changed

2 files changed

+248
-1
lines changed

harp/model.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from enum import Enum
88
from typing import Annotated, Dict, List, Optional, Union
99

10+
from pydantic import (BaseModel, BeforeValidator, ConfigDict, Field, RootModel,
11+
field_serializer)
12+
from typing import Dict, List, Optional, Union
13+
1014
from pydantic import (BaseModel, BeforeValidator, ConfigDict, Field, RootModel,
1115
field_serializer)
1216
from typing_extensions import Annotated

harp/reader.py

Lines changed: 244 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
from math import log2
77
from os import PathLike
88
from pathlib import Path
9-
from typing import Any, BinaryIO, Callable, Iterable, Mapping, Optional, Protocol, Union
9+
from typing import (Any, BinaryIO, Callable, Iterable, Mapping, Optional,
10+
Protocol, Union)
1011

12+
import requests
13+
from deprecated import deprecated
1114
from numpy import dtype
1215
from pandas import DataFrame, Series
1316
from pandas._typing import Axes
1417

18+
from harp import __version__
1519
from harp.io import MessageType, read
1620
from harp.model import BitMask, GroupMask, Model, PayloadMember, Register
1721
from harp.schema import read_schema
@@ -313,6 +317,245 @@ def from_dataset(
313317
"The dataset must be a directory containing a device.yml file."
314318
)
315319

320+
@staticmethod
321+
def from_file(
322+
filepath: PathLike,
323+
base_path: Optional[PathLike] = None,
324+
include_common_registers: bool = True,
325+
epoch: Optional[datetime] = None,
326+
keep_type: bool = False,
327+
) -> "DeviceReader":
328+
"""Creates a device reader object from the specified schema yml file.
329+
330+
Parameters
331+
----------
332+
filepath
333+
A path to the device yml schema describing the device.
334+
base_path
335+
The path to attempt to resolve the location of data files.
336+
include_common_registers
337+
Specifies whether to include the set of Harp common registers in the
338+
parsed device schema object. If a parsed device schema object is provided,
339+
this parameter is ignored.
340+
epoch
341+
The default reference datetime at which time zero begins. If specified,
342+
the data frames returned by each register reader will have a datetime index.
343+
keep_type
344+
Specifies whether to include a column with the message type by default.
345+
346+
Returns
347+
-------
348+
A device reader object which can be used to read binary data for each
349+
register or to access metadata about each register. Individual registers
350+
can be accessed using dot notation using the name of the register as the
351+
key.
352+
"""
353+
354+
device = read_schema(filepath, include_common_registers)
355+
if base_path is None:
356+
path = Path(filepath).absolute().resolve()
357+
base_path = path.parent / device.device
358+
else:
359+
base_path = Path(base_path).absolute().resolve() / device.device
360+
361+
reg_readers = {
362+
name: _create_register_parser(
363+
device, name, _ReaderParams(base_path, epoch, keep_type)
364+
)
365+
for name in device.registers.keys()
366+
}
367+
return DeviceReader(device, reg_readers)
368+
369+
@staticmethod
370+
def from_url(
371+
url: str,
372+
base_path: Optional[PathLike] = None,
373+
include_common_registers: bool = True,
374+
epoch: Optional[datetime] = None,
375+
keep_type: bool = False,
376+
timeout: int = 5,
377+
) -> "DeviceReader":
378+
"""Creates a device reader object from a url pointing to a device.yml file.
379+
380+
Parameters
381+
----------
382+
url
383+
The url pointing to the device.yml schema describing the device.
384+
base_path
385+
The path to attempt to resolve the location of data files.
386+
include_common_registers
387+
Specifies whether to include the set of Harp common registers in the
388+
parsed device schema object. If a parsed device schema object is provided,
389+
this parameter is ignored.
390+
epoch
391+
The default reference datetime at which time zero begins. If specified,
392+
the data frames returned by each register reader will have a datetime index.
393+
keep_type
394+
Specifies whether to include a column with the message type by default.
395+
timeout
396+
The number of seconds to wait for the server to send data before giving up.
397+
Returns
398+
-------
399+
A device reader object which can be used to read binary data for each
400+
register or to access metadata about each register. Individual registers
401+
can be accessed using dot notation using the name of the register as the
402+
key.
403+
"""
404+
405+
response = requests.get(url, timeout=timeout)
406+
text = response.text
407+
408+
device = read_schema(text, include_common_registers)
409+
if base_path is None:
410+
base_path = Path(device.device).absolute().resolve()
411+
else:
412+
base_path = Path(base_path).absolute().resolve()
413+
414+
reg_readers = {
415+
name: _create_register_parser(
416+
device, name, _ReaderParams(base_path, epoch, keep_type)
417+
)
418+
for name in device.registers.keys()
419+
}
420+
return DeviceReader(device, reg_readers)
421+
422+
@staticmethod
423+
def from_str(
424+
schema: str,
425+
base_path: Optional[PathLike] = None,
426+
include_common_registers: bool = True,
427+
epoch: Optional[datetime] = None,
428+
keep_type: bool = False,
429+
) -> "DeviceReader":
430+
"""Creates a device reader object from a string containing a device.yml schema.
431+
432+
Parameters
433+
----------
434+
schema
435+
The string containing the device.yml schema describing the device.
436+
base_path
437+
The path to attempt to resolve the location of data files.
438+
include_common_registers
439+
Specifies whether to include the set of Harp common registers in the
440+
parsed device schema object. If a parsed device schema object is provided,
441+
this parameter is ignored.
442+
epoch
443+
The default reference datetime at which time zero begins. If specified,
444+
the data frames returned by each register reader will have a datetime index.
445+
keep_type
446+
Specifies whether to include a column with the message type by default.
447+
448+
Returns
449+
-------
450+
A device reader object which can be used to read binary data for each
451+
register or to access metadata about each register. Individual registers
452+
can be accessed using dot notation using the name of the register as the
453+
key.
454+
"""
455+
456+
device = read_schema(schema, include_common_registers)
457+
if base_path is None:
458+
base_path = Path(device.device).absolute().resolve()
459+
else:
460+
base_path = Path(base_path).absolute().resolve()
461+
462+
reg_readers = {
463+
name: _create_register_parser(
464+
device, name, _ReaderParams(base_path, epoch, keep_type)
465+
)
466+
for name in device.registers.keys()
467+
}
468+
return DeviceReader(device, reg_readers)
469+
470+
@staticmethod
471+
def from_model(
472+
model: Model,
473+
base_path: Optional[PathLike] = None,
474+
epoch: Optional[datetime] = None,
475+
keep_type: bool = False,
476+
) -> "DeviceReader":
477+
"""Creates a device reader object from a parsed device schema object.
478+
479+
Parameters
480+
----------
481+
model
482+
The parsed device schema object describing the device.
483+
base_path
484+
The path to attempt to resolve the location of data files.
485+
epoch
486+
The default reference datetime at which time zero begins. If specified,
487+
the data frames returned by each register reader will have a datetime index.
488+
keep_type
489+
Specifies whether to include a column with the message type by default.
490+
491+
Returns
492+
-------
493+
A device reader object which can be used to read binary data for each
494+
register or to access metadata about each register. Individual registers
495+
can be accessed using dot notation using the name of the register as the
496+
key.
497+
"""
498+
499+
if base_path is None:
500+
base_path = Path(model.device).absolute().resolve()
501+
else:
502+
base_path = Path(base_path).absolute().resolve()
503+
504+
reg_readers = {
505+
name: _create_register_parser(
506+
model, name, _ReaderParams(base_path, epoch, keep_type)
507+
)
508+
for name in model.registers.keys()
509+
}
510+
return DeviceReader(model, reg_readers)
511+
512+
@staticmethod
513+
def from_dataset(
514+
dataset: PathLike,
515+
include_common_registers: bool = True,
516+
epoch: Optional[datetime] = None,
517+
keep_type: bool = False,
518+
) -> "DeviceReader":
519+
"""Creates a device reader object from the specified dataset folder.
520+
521+
Parameters
522+
----------
523+
dataset
524+
A path to the dataset folder containing a device.yml schema describing the device.
525+
include_common_registers
526+
Specifies whether to include the set of Harp common registers in the
527+
parsed device schema object. If a parsed device schema object is provided,
528+
this parameter is ignored.
529+
epoch
530+
The default reference datetime at which time zero begins. If specified,
531+
the data frames returned by each register reader will have a datetime index.
532+
keep_type
533+
Specifies whether to include a column with the message type by default.
534+
535+
Returns
536+
-------
537+
A device reader object which can be used to read binary data for each
538+
register or to access metadata about each register. Individual registers
539+
can be accessed using dot notation using the name of the register as the
540+
key.
541+
"""
542+
543+
path = Path(dataset).absolute().resolve()
544+
is_dir = os.path.isdir(path)
545+
if is_dir:
546+
filepath = path / "device.yml"
547+
return DeviceReader.from_file(
548+
filepath=filepath,
549+
base_path=path,
550+
include_common_registers=include_common_registers,
551+
epoch=epoch,
552+
keep_type=keep_type,
553+
)
554+
else:
555+
raise ValueError(
556+
"The dataset must be a directory containing a device.yml file."
557+
)
558+
316559

317560
def _compose_parser(
318561
f: Callable[[DataFrame], DataFrame],

0 commit comments

Comments
 (0)