lynguine 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lynguine might be problematic. Click here for more details.

Files changed (112) hide show
  1. lynguine-0.1.0/PKG-INFO +191 -0
  2. lynguine-0.1.0/README.md +152 -0
  3. lynguine-0.1.0/lynguine/.coverage +0 -0
  4. lynguine-0.1.0/lynguine/__init__.py +10 -0
  5. lynguine-0.1.0/lynguine/access/__pycache__/__init__.cpython-311.pyc +0 -0
  6. lynguine-0.1.0/lynguine/access/__pycache__/download.cpython-311.pyc +0 -0
  7. lynguine-0.1.0/lynguine/access/__pycache__/io.cpython-311.pyc +0 -0
  8. lynguine-0.1.0/lynguine/access/__pycache__/io.cpython-312.pyc +0 -0
  9. lynguine-0.1.0/lynguine/access/download.py +311 -0
  10. lynguine-0.1.0/lynguine/access/io.py +1912 -0
  11. lynguine-0.1.0/lynguine/assess/__pycache__/compute.cpython-311.pyc +0 -0
  12. lynguine-0.1.0/lynguine/assess/__pycache__/compute.cpython-312.pyc +0 -0
  13. lynguine-0.1.0/lynguine/assess/__pycache__/data.cpython-311.pyc +0 -0
  14. lynguine-0.1.0/lynguine/assess/__pycache__/data.cpython-312.pyc +0 -0
  15. lynguine-0.1.0/lynguine/assess/compute.py +412 -0
  16. lynguine-0.1.0/lynguine/assess/data.py +2957 -0
  17. lynguine-0.1.0/lynguine/clone_or_pull.py +31 -0
  18. lynguine-0.1.0/lynguine/compute.py +225 -0
  19. lynguine-0.1.0/lynguine/config/__pycache__/context.cpython-311.pyc +0 -0
  20. lynguine-0.1.0/lynguine/config/__pycache__/context.cpython-312.pyc +0 -0
  21. lynguine-0.1.0/lynguine/config/__pycache__/interface.cpython-311.pyc +0 -0
  22. lynguine-0.1.0/lynguine/config/__pycache__/interface.cpython-312.pyc +0 -0
  23. lynguine-0.1.0/lynguine/config/__pycache__/settings.cpython-311.pyc +0 -0
  24. lynguine-0.1.0/lynguine/config/context.py +213 -0
  25. lynguine-0.1.0/lynguine/config/defaults.yml +31 -0
  26. lynguine-0.1.0/lynguine/config/interface.py +467 -0
  27. lynguine-0.1.0/lynguine/convert.py +36 -0
  28. lynguine-0.1.0/lynguine/coverage.xml +3226 -0
  29. lynguine-0.1.0/lynguine/log.py +44 -0
  30. lynguine-0.1.0/lynguine/ndlappointment.py +309 -0
  31. lynguine-0.1.0/lynguine/ndlcal.py +98 -0
  32. lynguine-0.1.0/lynguine/ndlcgi.py +285 -0
  33. lynguine-0.1.0/lynguine/ndlconvert.py +25 -0
  34. lynguine-0.1.0/lynguine/ndlexpenses.py +51 -0
  35. lynguine-0.1.0/lynguine/ndlimage.py +51 -0
  36. lynguine-0.1.0/lynguine/ndlmarkdown.py +51 -0
  37. lynguine-0.1.0/lynguine/ndlstrutil.py +53 -0
  38. lynguine-0.1.0/lynguine/ndltext.py +42 -0
  39. lynguine-0.1.0/lynguine/old_data.py +84 -0
  40. lynguine-0.1.0/lynguine/ruby/Gemfile +4 -0
  41. lynguine-0.1.0/lynguine/ruby/json2yaml.rb +39 -0
  42. lynguine-0.1.0/lynguine/test.xlsx +0 -0
  43. lynguine-0.1.0/lynguine/tests/__pycache__/test_access_download.cpython-311-pytest-6.2.5.pyc +0 -0
  44. lynguine-0.1.0/lynguine/tests/__pycache__/test_access_io.cpython-311-pytest-6.2.5.pyc +0 -0
  45. lynguine-0.1.0/lynguine/tests/__pycache__/test_access_io.cpython-311.pyc +0 -0
  46. lynguine-0.1.0/lynguine/tests/__pycache__/test_assess_compute.cpython-311-pytest-6.2.5.pyc +0 -0
  47. lynguine-0.1.0/lynguine/tests/__pycache__/test_assess_data.cpython-311-pytest-6.2.5.pyc +0 -0
  48. lynguine-0.1.0/lynguine/tests/__pycache__/test_assess_data.cpython-311-pytest-7.4.0.pyc +0 -0
  49. lynguine-0.1.0/lynguine/tests/__pycache__/test_assess_data.cpython-311.pyc +0 -0
  50. lynguine-0.1.0/lynguine/tests/__pycache__/test_assess_data_compute.cpython-311-pytest-6.2.5.pyc +0 -0
  51. lynguine-0.1.0/lynguine/tests/__pycache__/test_assess_data_compute.cpython-311.pyc +0 -0
  52. lynguine-0.1.0/lynguine/tests/__pycache__/test_config_context.cpython-311-pytest-6.2.5.pyc +0 -0
  53. lynguine-0.1.0/lynguine/tests/__pycache__/test_config_interface.cpython-311-pytest-6.2.5.pyc +0 -0
  54. lynguine-0.1.0/lynguine/tests/__pycache__/test_config_settings.cpython-311-pytest-6.2.5.pyc +0 -0
  55. lynguine-0.1.0/lynguine/tests/__pycache__/test_customdataframe_integration.cpython-311-pytest-6.2.5.pyc +0 -0
  56. lynguine-0.1.0/lynguine/tests/__pycache__/test_util_dataframe.cpython-311-pytest-6.2.5.pyc +0 -0
  57. lynguine-0.1.0/lynguine/tests/__pycache__/test_util_files.cpython-311-pytest-6.2.5.pyc +0 -0
  58. lynguine-0.1.0/lynguine/tests/__pycache__/test_util_liquid.cpython-311-pytest-6.2.5.pyc +0 -0
  59. lynguine-0.1.0/lynguine/tests/__pycache__/test_util_misc.cpython-311-pytest-6.2.5.pyc +0 -0
  60. lynguine-0.1.0/lynguine/tests/__pycache__/test_util_talk.cpython-311-pytest-6.2.5.pyc +0 -0
  61. lynguine-0.1.0/lynguine/tests/__pycache__/test_util_tex.cpython-311-pytest-6.2.5.pyc +0 -0
  62. lynguine-0.1.0/lynguine/tests/__pycache__/test_util_yaml.cpython-311-pytest-6.2.5.pyc +0 -0
  63. lynguine-0.1.0/lynguine/tests/linguine.log +31 -0
  64. lynguine-0.1.0/lynguine/tests/talk_tests.py +27 -0
  65. lynguine-0.1.0/lynguine/tests/test.xlsx +0 -0
  66. lynguine-0.1.0/lynguine/tests/test_access_download.py +220 -0
  67. lynguine-0.1.0/lynguine/tests/test_access_io.py +1320 -0
  68. lynguine-0.1.0/lynguine/tests/test_assess_compute.py +193 -0
  69. lynguine-0.1.0/lynguine/tests/test_assess_data.py +634 -0
  70. lynguine-0.1.0/lynguine/tests/test_assess_data_compute.py +88 -0
  71. lynguine-0.1.0/lynguine/tests/test_config_context.py +54 -0
  72. lynguine-0.1.0/lynguine/tests/test_config_interface.py +254 -0
  73. lynguine-0.1.0/lynguine/tests/test_customdataframe_integration.py +70 -0
  74. lynguine-0.1.0/lynguine/tests/test_util_dataframe.py +162 -0
  75. lynguine-0.1.0/lynguine/tests/test_util_files.py +70 -0
  76. lynguine-0.1.0/lynguine/tests/test_util_liquid.py +70 -0
  77. lynguine-0.1.0/lynguine/tests/test_util_misc.py +253 -0
  78. lynguine-0.1.0/lynguine/tests/test_util_talk.py +126 -0
  79. lynguine-0.1.0/lynguine/tests/test_util_tex.py +113 -0
  80. lynguine-0.1.0/lynguine/tests/test_util_yaml.py +78 -0
  81. lynguine-0.1.0/lynguine/util/.coverage +0 -0
  82. lynguine-0.1.0/lynguine/util/__pycache__/dataframe.cpython-311.pyc +0 -0
  83. lynguine-0.1.0/lynguine/util/__pycache__/dataframe.cpython-312.pyc +0 -0
  84. lynguine-0.1.0/lynguine/util/__pycache__/fake.cpython-311.pyc +0 -0
  85. lynguine-0.1.0/lynguine/util/__pycache__/fake.cpython-312.pyc +0 -0
  86. lynguine-0.1.0/lynguine/util/__pycache__/files.cpython-311.pyc +0 -0
  87. lynguine-0.1.0/lynguine/util/__pycache__/files.cpython-312.pyc +0 -0
  88. lynguine-0.1.0/lynguine/util/__pycache__/html.cpython-311.pyc +0 -0
  89. lynguine-0.1.0/lynguine/util/__pycache__/html.cpython-312.pyc +0 -0
  90. lynguine-0.1.0/lynguine/util/__pycache__/liquid.cpython-311.pyc +0 -0
  91. lynguine-0.1.0/lynguine/util/__pycache__/liquid.cpython-312.pyc +0 -0
  92. lynguine-0.1.0/lynguine/util/__pycache__/misc.cpython-311.pyc +0 -0
  93. lynguine-0.1.0/lynguine/util/__pycache__/misc.cpython-312.pyc +0 -0
  94. lynguine-0.1.0/lynguine/util/__pycache__/talk.cpython-311.pyc +0 -0
  95. lynguine-0.1.0/lynguine/util/__pycache__/talk.cpython-312.pyc +0 -0
  96. lynguine-0.1.0/lynguine/util/__pycache__/tex.cpython-311.pyc +0 -0
  97. lynguine-0.1.0/lynguine/util/__pycache__/tex.cpython-312.pyc +0 -0
  98. lynguine-0.1.0/lynguine/util/__pycache__/text.cpython-311.pyc +0 -0
  99. lynguine-0.1.0/lynguine/util/__pycache__/text.cpython-312.pyc +0 -0
  100. lynguine-0.1.0/lynguine/util/__pycache__/yaml.cpython-311.pyc +0 -0
  101. lynguine-0.1.0/lynguine/util/__pycache__/yaml.cpython-312.pyc +0 -0
  102. lynguine-0.1.0/lynguine/util/dataframe.py +401 -0
  103. lynguine-0.1.0/lynguine/util/fake.py +713 -0
  104. lynguine-0.1.0/lynguine/util/files.py +177 -0
  105. lynguine-0.1.0/lynguine/util/html.py +75 -0
  106. lynguine-0.1.0/lynguine/util/liquid.py +104 -0
  107. lynguine-0.1.0/lynguine/util/misc.py +374 -0
  108. lynguine-0.1.0/lynguine/util/talk.py +192 -0
  109. lynguine-0.1.0/lynguine/util/tex.py +394 -0
  110. lynguine-0.1.0/lynguine/util/text.py +12 -0
  111. lynguine-0.1.0/lynguine/util/yaml.py +81 -0
  112. lynguine-0.1.0/pyproject.toml +60 -0
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.1
2
+ Name: lynguine
3
+ Version: 0.1.0
4
+ Summary: Package for linguine data oriented architecture interfaces.
5
+ Home-page: https://github.com/lawrennd/linguine
6
+ License: MIT
7
+ Author: Neil D. Lawrence
8
+ Author-email: lawrennd@gmail.com
9
+ Requires-Python: >=3.11,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
16
+ Requires-Dist: XlsxWriter
17
+ Requires-Dist: anyascii
18
+ Requires-Dist: bibtexparser
19
+ Requires-Dist: gitpython
20
+ Requires-Dist: google-api-python-client
21
+ Requires-Dist: google-auth-httplib2
22
+ Requires-Dist: gspread-pandas
23
+ Requires-Dist: markdown
24
+ Requires-Dist: markdownify
25
+ Requires-Dist: mimesis (>11)
26
+ Requires-Dist: numpy (>=1.0.0,<2.0.0)
27
+ Requires-Dist: openpyxl
28
+ Requires-Dist: pandas
29
+ Requires-Dist: pylatexenc
30
+ Requires-Dist: pyminizip
31
+ Requires-Dist: pypandoc
32
+ Requires-Dist: python-frontmatter
33
+ Requires-Dist: python-liquid
34
+ Requires-Dist: pyyaml
35
+ Requires-Dist: pyzmq (>=25.1.2,<26.0.0)
36
+ Requires-Dist: wget
37
+ Description-Content-Type: text/markdown
38
+
39
+ # linguine
40
+
41
+
42
+ ![Tests](https://github.com/lawrennd/linguine/actions/workflows/python-tests.yml/badge.svg)
43
+
44
+
45
+ [![codecov](https://codecov.io/gh/lawrennd/linguine/branch/main/graph/badge.svg?token=YOUR_CODECOV_TOKEN)](https://codecov.io/gh/lawrennd/linguine)
46
+
47
+ The `linguine` library is a library based on DOA principles for managing data. It provides these capabilities to support other libraries like `lamd` and `referia`.
48
+ To install use
49
+
50
+ ```bash
51
+ %pip install linguine
52
+ ```
53
+
54
+ The softare consists of TK principle parts.
55
+
56
+ ### Config
57
+
58
+ First `config` which consists of `interface` and `context`.
59
+
60
+ `context` defines the `Context` object which is used to store information about the context, such as machine type etc. On the other hand`interface` defines the `Interface` object that's used for defining inputs and outputs which defines connections to other 'black box processes'.
61
+
62
+ #### A short example
63
+
64
+ The local context can be loaded in using the following command.
65
+ ```
66
+ import linguine as ln
67
+
68
+ ctxt = ln.config.context.Context()
69
+ ```
70
+
71
+ The `interface` module contains the key structure of `linguine`. It specifies incoming and outgoing flows, as well as computational operations. Each flow is specified in the following form.
72
+
73
+ ```yaml
74
+ input:
75
+ source:
76
+ ```
77
+
78
+ Preprocessing can be done with a compute field.
79
+
80
+ ```yaml
81
+ input:
82
+ compute:
83
+ field: ColumnName0
84
+ function: computeFunction
85
+ args:
86
+ arg1: argument1
87
+ arg2: argument2
88
+ row_args:
89
+ arg3: ColumnName1
90
+ ```
91
+
92
+ Often the data will be stored in another file (csv, excel, yaml etc) but sometimes it's convenient to store it as a `local` in a field calld 'data'. In the next example we do this to illustrate how the `compute` capability can be used to augment the file. Here two fields are added, the full name (used as an index) and today's date as an access date.
93
+
94
+ ```python
95
+ import yaml
96
+ from linguine.config.interface import Interface
97
+ from linguine.assess.data import CustomDataFrame
98
+
99
+ # Let's assume this is the text stored in the interface file
100
+ yaml_text = """input:
101
+ type: local
102
+ index: fullName
103
+ data:
104
+ - familyName: Xing
105
+ givenName: Pei
106
+ - familyName: Venkatasubramanian
107
+ givenName: Siva
108
+ - familyName: Paz Luiz
109
+ givenName: Miguel
110
+ compute:
111
+ - field: fullName
112
+ function: render_liquid
113
+ args:
114
+ template: '{{familyName | replace: " ", "-"}}_{{givenName | replace: " ", "-"}}'
115
+ row_args:
116
+ givenName: givenName
117
+ familyName: familyName
118
+ - field: accessDate
119
+ function: today"""
120
+
121
+ interface = Interface(yaml.safe_load(yaml_text))
122
+
123
+ data = CustomDataFrame.from_flow(interface)
124
+ print(data)
125
+ ```
126
+
127
+ would create a new field fullname which is then used as the index.
128
+
129
+ ### Access
130
+
131
+ Secondly the software uses the access, assess, address decomposition. Where `access` is used for accessing data and consists of `io` and `download`. `io` allows for reading from and writing to various different file formats such as `json`, `yaml`, `markdown`, `csv`, `xls`, `bibtex`.
132
+
133
+ `download` is for accessing resources from the web, such as downloading a specific url.
134
+
135
+ #### A short example
136
+
137
+ Perhaps you would like to create a bibtex file from the PMLR proceedings volume 1, Gaussian processes in practice. In the short example below, we use `linguine` to first download the relevant URL, then we load it in and save as bibtex.
138
+
139
+ ```python
140
+ import linguine
141
+ ```
142
+
143
+
144
+ ### Assess
145
+
146
+ Assess is about taking the raw data and processing it. Under `assess` `linguine` provides `data` and `compute`. The `data` module provides a `CustomDataFrame` object that provides access to the data and manipulation capabilities. the `compute` module wraps various compute capabilities for preprocessing and processing the data.
147
+
148
+ #### A short example
149
+
150
+ ```python
151
+ import linguine
152
+ ```
153
+
154
+ ### Util
155
+
156
+ The `util` module provides various utilities for working with data. They include
157
+
158
+ * `dataframe` for operating on data frames.
159
+ * `fake` for generating fake data.
160
+ * `files` for interacting with files.
161
+ * `html` for working with html.
162
+ * `liquid` for working with the liquid template language.
163
+ * `talk` for working with Neil's talk format.
164
+ * `tex` for working with latex.
165
+ * `text` for working with text.
166
+ * `yaml` for working with yaml.
167
+ * `misc` for other miscellaneous utilities.
168
+
169
+ ### A short example
170
+
171
+ ```python
172
+ import linguine
173
+ ```
174
+
175
+ ### Tests
176
+
177
+ The tests are stored in the `tests` subdirectory. They use `pytest`.
178
+
179
+
180
+ #### A short example
181
+
182
+ If you have poetry installed you can run the tests using
183
+
184
+ ```bash
185
+ poetry run pytest
186
+ ```
187
+
188
+ ### Why linguine?
189
+
190
+ The name comes from the idea that data oriented architecture is like a set of streams of data, like linguine pasta. In Italian the word also means "little toungues" so there's also a connocation of translation between services.
191
+
@@ -0,0 +1,152 @@
1
+ # linguine
2
+
3
+
4
+ ![Tests](https://github.com/lawrennd/linguine/actions/workflows/python-tests.yml/badge.svg)
5
+
6
+
7
+ [![codecov](https://codecov.io/gh/lawrennd/linguine/branch/main/graph/badge.svg?token=YOUR_CODECOV_TOKEN)](https://codecov.io/gh/lawrennd/linguine)
8
+
9
+ The `linguine` library is a library based on DOA principles for managing data. It provides these capabilities to support other libraries like `lamd` and `referia`.
10
+ To install use
11
+
12
+ ```bash
13
+ %pip install linguine
14
+ ```
15
+
16
+ The softare consists of TK principle parts.
17
+
18
+ ### Config
19
+
20
+ First `config` which consists of `interface` and `context`.
21
+
22
+ `context` defines the `Context` object which is used to store information about the context, such as machine type etc. On the other hand`interface` defines the `Interface` object that's used for defining inputs and outputs which defines connections to other 'black box processes'.
23
+
24
+ #### A short example
25
+
26
+ The local context can be loaded in using the following command.
27
+ ```
28
+ import linguine as ln
29
+
30
+ ctxt = ln.config.context.Context()
31
+ ```
32
+
33
+ The `interface` module contains the key structure of `linguine`. It specifies incoming and outgoing flows, as well as computational operations. Each flow is specified in the following form.
34
+
35
+ ```yaml
36
+ input:
37
+ source:
38
+ ```
39
+
40
+ Preprocessing can be done with a compute field.
41
+
42
+ ```yaml
43
+ input:
44
+ compute:
45
+ field: ColumnName0
46
+ function: computeFunction
47
+ args:
48
+ arg1: argument1
49
+ arg2: argument2
50
+ row_args:
51
+ arg3: ColumnName1
52
+ ```
53
+
54
+ Often the data will be stored in another file (csv, excel, yaml etc) but sometimes it's convenient to store it as a `local` in a field calld 'data'. In the next example we do this to illustrate how the `compute` capability can be used to augment the file. Here two fields are added, the full name (used as an index) and today's date as an access date.
55
+
56
+ ```python
57
+ import yaml
58
+ from linguine.config.interface import Interface
59
+ from linguine.assess.data import CustomDataFrame
60
+
61
+ # Let's assume this is the text stored in the interface file
62
+ yaml_text = """input:
63
+ type: local
64
+ index: fullName
65
+ data:
66
+ - familyName: Xing
67
+ givenName: Pei
68
+ - familyName: Venkatasubramanian
69
+ givenName: Siva
70
+ - familyName: Paz Luiz
71
+ givenName: Miguel
72
+ compute:
73
+ - field: fullName
74
+ function: render_liquid
75
+ args:
76
+ template: '{{familyName | replace: " ", "-"}}_{{givenName | replace: " ", "-"}}'
77
+ row_args:
78
+ givenName: givenName
79
+ familyName: familyName
80
+ - field: accessDate
81
+ function: today"""
82
+
83
+ interface = Interface(yaml.safe_load(yaml_text))
84
+
85
+ data = CustomDataFrame.from_flow(interface)
86
+ print(data)
87
+ ```
88
+
89
+ would create a new field fullname which is then used as the index.
90
+
91
+ ### Access
92
+
93
+ Secondly the software uses the access, assess, address decomposition. Where `access` is used for accessing data and consists of `io` and `download`. `io` allows for reading from and writing to various different file formats such as `json`, `yaml`, `markdown`, `csv`, `xls`, `bibtex`.
94
+
95
+ `download` is for accessing resources from the web, such as downloading a specific url.
96
+
97
+ #### A short example
98
+
99
+ Perhaps you would like to create a bibtex file from the PMLR proceedings volume 1, Gaussian processes in practice. In the short example below, we use `linguine` to first download the relevant URL, then we load it in and save as bibtex.
100
+
101
+ ```python
102
+ import linguine
103
+ ```
104
+
105
+
106
+ ### Assess
107
+
108
+ Assess is about taking the raw data and processing it. Under `assess` `linguine` provides `data` and `compute`. The `data` module provides a `CustomDataFrame` object that provides access to the data and manipulation capabilities. the `compute` module wraps various compute capabilities for preprocessing and processing the data.
109
+
110
+ #### A short example
111
+
112
+ ```python
113
+ import linguine
114
+ ```
115
+
116
+ ### Util
117
+
118
+ The `util` module provides various utilities for working with data. They include
119
+
120
+ * `dataframe` for operating on data frames.
121
+ * `fake` for generating fake data.
122
+ * `files` for interacting with files.
123
+ * `html` for working with html.
124
+ * `liquid` for working with the liquid template language.
125
+ * `talk` for working with Neil's talk format.
126
+ * `tex` for working with latex.
127
+ * `text` for working with text.
128
+ * `yaml` for working with yaml.
129
+ * `misc` for other miscellaneous utilities.
130
+
131
+ ### A short example
132
+
133
+ ```python
134
+ import linguine
135
+ ```
136
+
137
+ ### Tests
138
+
139
+ The tests are stored in the `tests` subdirectory. They use `pytest`.
140
+
141
+
142
+ #### A short example
143
+
144
+ If you have poetry installed you can run the tests using
145
+
146
+ ```bash
147
+ poetry run pytest
148
+ ```
149
+
150
+ ### Why linguine?
151
+
152
+ The name comes from the idea that data oriented architecture is like a set of streams of data, like linguine pasta. In Italian the word also means "little toungues" so there's also a connocation of translation between services.
Binary file
@@ -0,0 +1,10 @@
1
+ from .util import yaml
2
+ from .util import talk
3
+ from .util import tex
4
+ from .util import misc
5
+ from .util import files
6
+ from .util import html
7
+ from . import log
8
+ from .config import context
9
+ from .config import interface
10
+ from .access import io
@@ -0,0 +1,311 @@
1
+ import os
2
+
3
+ from urllib.request import urlopen
4
+ from urllib.error import HTTPError, URLError
5
+ import git
6
+
7
+ from linguine.util.misc import prompt_stdin
8
+ from ..log import Logger
9
+ from ..config.context import Context
10
+
11
+ from ..config.interface import Interface
12
+
13
+ ctxt = Context()
14
+ log = Logger(
15
+ name=__name__,
16
+ level=ctxt._data["logging"]["level"],
17
+ filename=ctxt._data["logging"]["filename"],
18
+ )
19
+
20
+
21
+ class FileDownloader:
22
+ """
23
+ A class for downloading data files from a url.
24
+ """
25
+
26
+ def __init__(self, interface, data_resources, data_name):
27
+ """
28
+ Initialize the FileDownloader class.
29
+ :param data_resources: The data resources dictionary.
30
+ :param data_name: The name of the data to download.
31
+ """
32
+ self.interface = interface
33
+ self.data_resources = data_resources
34
+ self.data_name = data_name
35
+ if self.data_name not in self.data_resources:
36
+ raise ValueError(f'Data "{self.data_name}" not found.')
37
+ self._dr = self.data_resources[self.data_name]
38
+
39
+ @property
40
+ def interface(self):
41
+ """
42
+ Return the interface object.
43
+ :return: The interface object.
44
+ """
45
+ return self._interface
46
+
47
+ @interface.setter
48
+ def interface(self, value):
49
+ """
50
+ Set the interface object.
51
+ :param value: The interface object.
52
+ :return: None
53
+ """
54
+ if not isinstance(value, Interface):
55
+ raise TypeError("interface must be of type Interface.")
56
+ self._interface = value
57
+
58
+ @property
59
+ def data_name(self):
60
+ """
61
+ Return the name of the data to download.
62
+ :return: The name of the data to download.
63
+ """
64
+ return self._data_name
65
+
66
+ @data_name.setter
67
+ def data_name(self, value):
68
+ """
69
+ Set the name of the data to download.
70
+ :param value: The name of the data to download.
71
+ :return: None
72
+ """
73
+ self._data_name = value
74
+
75
+ @property
76
+ def data_resources(self):
77
+ """
78
+ Return the data resources dictionary.
79
+ :return: The data resources dictionary.
80
+ """
81
+ return self._data_resources
82
+
83
+ @data_resources.setter
84
+ def data_resources(self, value):
85
+ """
86
+ Set the data resources dictionary.
87
+ :param value: The data resources dictionary.
88
+ :return: None
89
+ """
90
+ self._data_resources = value
91
+
92
+ def _authorize_download(self, prompt=None):
93
+ """
94
+ Check with the user that they agree to terms and conditions for the data.
95
+ :param data_name: The name of the data to download.
96
+ :param prompt: A function that takes a string and returns a boolean.
97
+ :return: True if the user agrees to the terms and conditions.
98
+ :raises: ValueError if the data is not found.
99
+ """
100
+
101
+ if prompt is None:
102
+ prompt = prompt_stdin
103
+
104
+ print(f"Acquiring resource: {self.data_name}\n")
105
+ print("Details of data: ")
106
+ print(self._dr.get("details", "No details available."))
107
+
108
+ if self._dr.get("citation"):
109
+ print("\nPlease cite:\n" + self._dr["citation"])
110
+ if self._dr.get("size"):
111
+ print(
112
+ f"\nAfter downloading, the data will take up {self._dr['size']} bytes of space."
113
+ )
114
+
115
+ storage_path = os.path.join(self.interface["default_cache_path"], self.data_name)
116
+ print(f"\nData will be stored in {storage_path}.\n")
117
+
118
+ if self.interface["overide_manual_authorize"]:
119
+ if self._dr.get("license"):
120
+ print(
121
+ "You have agreed to the following license:\n" + self._dr["license"]
122
+ )
123
+ return True
124
+ else:
125
+ if self._dr.get("license"):
126
+ print(
127
+ "You must also agree to the following license:\n"
128
+ + self._dr["license"]
129
+ )
130
+ return prompt("Do you wish to proceed with the download? [yes/no] ")
131
+
132
+ def _download_url(
133
+ self,
134
+ url,
135
+ dir_name=".",
136
+ save_name=None,
137
+ store_directory=None,
138
+ messages=True,
139
+ suffix="",
140
+ ):
141
+ """
142
+ Download a file from a url and save it to disk.
143
+
144
+ :param url: The url to download from.
145
+ :param dir_name: The directory to save the file to.
146
+ :param save_name: The name to save the file as.
147
+ :param store_directory: The directory to store the file in.
148
+ :param messages: Whether to print messages to the console.
149
+ :param suffix: The suffix to add to the url.
150
+ :return: None
151
+ """
152
+ file_name = os.path.basename(url)
153
+ if store_directory is not None:
154
+ dir_name = os.path.join(dir_name, store_directory)
155
+ if save_name is None:
156
+ save_name = file_name
157
+ save_path = os.path.join(dir_name, save_name)
158
+
159
+ if not os.path.exists(dir_name):
160
+ os.makedirs(dir_name)
161
+
162
+ try:
163
+ response = urlopen(url + suffix)
164
+ except HTTPError as e:
165
+ raise ValueError(f"HTTP error {e.code} when accessing {url}")
166
+ except URLError as e:
167
+ raise ValueError(f"URL error {e.reason} when accessing {url}")
168
+
169
+ self._save_file(response, save_path)
170
+
171
+ def _save_file(self, response, save_path):
172
+ """
173
+ Save the file with progress bar.
174
+
175
+ :param response: The response object from urlopen.
176
+ :param save_path: The path to save the file to.
177
+ :return: None
178
+ """
179
+ try:
180
+ file_size = int(response.info().get("Content-Length"))
181
+ except (TypeError, ValueError):
182
+ file_size = None
183
+
184
+ with open(save_path, "wb") as f:
185
+ if file_size:
186
+ from tqdm import tqdm
187
+
188
+ with tqdm(total=file_size, unit="B", unit_scale=True) as bar:
189
+ for buff in iter(lambda: response.read(1024), b""):
190
+ f.write(buff)
191
+ bar.update(len(buff))
192
+ else:
193
+ f.write(response.read())
194
+
195
+ def download_data(self, prompt=prompt_stdin):
196
+ """
197
+ Check with the user that they are happy with terms and conditions for the data, then download it.
198
+ :param prompt: A function that takes a string and returns a boolean.
199
+ :return: None
200
+ :raises: ValueError if the data is not found.
201
+ """
202
+ if not self._authorize_download(prompt):
203
+ raise Exception("Permission to download data denied.")
204
+
205
+ self._process_data()
206
+
207
+ def _process_data(self):
208
+ """
209
+ Process the data for downloading based on its configuration.
210
+
211
+ :return: None
212
+ """
213
+ if "suffices" in self._dr:
214
+ self._download_suffices()
215
+ elif "dirs" in self._dr:
216
+ self._download_dirs()
217
+ else:
218
+ self._download_simple()
219
+
220
+ def _download_suffices(self):
221
+ """
222
+ Download a data with a suffices.
223
+
224
+ :return: None
225
+ """
226
+ for url, filenames, suffices in zip(
227
+ self._dr["urls"], self._dr["files"], self._dr["suffices"]
228
+ ):
229
+ for filename, suffix in zip(filenames, suffices):
230
+ self._download_file(url, filename, suffix=suffix)
231
+
232
+ def _download_dirs(self):
233
+ """
234
+ Download a data with directories.
235
+
236
+ :return: None
237
+ """
238
+ for url, dirnames, filenames in zip(
239
+ self._dr["urls"], self._dr["dirs"], self._dr["files"]
240
+ ):
241
+ for filename, dirname in zip(filenames, dirnames):
242
+ self._download_file(url, filename, dirname=dirname)
243
+
244
+ def _download_simple(self):
245
+ """
246
+ Download the data.
247
+
248
+ :return: None
249
+ """
250
+ for url, filenames in zip(self._dr["urls"], self._dr["files"]):
251
+ for filename in filenames:
252
+ self._download_file(url, filename)
253
+
254
+ def _download_file(self, url, filename, dirname=None, suffix=""):
255
+ """
256
+ Download a file from a url and save it to disk.
257
+
258
+ :param url: The url to download from.
259
+ :param filename: The name to save the file as.
260
+ :param dirname: The directory to store the file in.
261
+ :param suffix: The suffix to add to the url.
262
+ :return: None
263
+ """
264
+
265
+ full_url = os.path.join(url, dirname if dirname else "", filename).replace(
266
+ " ", "%20"
267
+ )
268
+ save_name = filename if not suffix else filename + suffix
269
+ self._download_url(
270
+ url=full_url,
271
+ store_directory=self.interface["default_cache_path"],
272
+ save_name=save_name,
273
+ )
274
+
275
+
276
+ class GitDownloader(FileDownloader):
277
+ def __init__(self, interface, data_resources, data_name, git_url):
278
+ super().__init__(interface, data_resources, data_name)
279
+ self._git_url = git_url
280
+ self._repo_path = self.interface["default_cache_path"]
281
+
282
+ def _process_data(self):
283
+ """
284
+ Process the data for downloading based on its configuration.
285
+
286
+ :return: None
287
+ """
288
+ self._clone_or_pull_repo()
289
+
290
+ def _clone_or_pull_repo(self):
291
+ """
292
+ Clone the repository or pull updates if it already exists.
293
+
294
+ :return: None
295
+ """
296
+ # Ensure the cache path exists
297
+ if not os.path.exists(self._repo_path):
298
+ os.makedirs(self._repo_path)
299
+
300
+ # Check if the repo already exists
301
+ if os.path.isdir(os.path.join(self._repo_path, '.git')):
302
+ try:
303
+ repo = git.Repo(self._repo_path)
304
+ repo.git.pull()
305
+ except Exception as e:
306
+ raise ValueError(f"Error pulling repository: {e}")
307
+ else:
308
+ try:
309
+ git.Repo.clone_from(self._git_url, self._repo_path)
310
+ except Exception as e:
311
+ raise ValueError(f"Error cloning repository: {e}")