dataforge-py 0.2.0__py3-none-any.whl

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.
Files changed (145) hide show
  1. dataforge/__init__.py +20 -0
  2. dataforge/backend.py +147 -0
  3. dataforge/cli.py +166 -0
  4. dataforge/core.py +1169 -0
  5. dataforge/locales/__init__.py +1 -0
  6. dataforge/locales/ar_SA/__init__.py +1 -0
  7. dataforge/locales/ar_SA/address.py +128 -0
  8. dataforge/locales/ar_SA/company.py +183 -0
  9. dataforge/locales/ar_SA/internet.py +25 -0
  10. dataforge/locales/ar_SA/person.py +217 -0
  11. dataforge/locales/ar_SA/phone.py +15 -0
  12. dataforge/locales/de_DE/__init__.py +1 -0
  13. dataforge/locales/de_DE/address.py +148 -0
  14. dataforge/locales/de_DE/company.py +125 -0
  15. dataforge/locales/de_DE/internet.py +32 -0
  16. dataforge/locales/de_DE/person.py +212 -0
  17. dataforge/locales/de_DE/phone.py +17 -0
  18. dataforge/locales/en_AU/__init__.py +1 -0
  19. dataforge/locales/en_AU/address.py +231 -0
  20. dataforge/locales/en_AU/company.py +193 -0
  21. dataforge/locales/en_AU/internet.py +34 -0
  22. dataforge/locales/en_AU/person.py +370 -0
  23. dataforge/locales/en_AU/phone.py +16 -0
  24. dataforge/locales/en_CA/__init__.py +1 -0
  25. dataforge/locales/en_CA/address.py +276 -0
  26. dataforge/locales/en_CA/company.py +193 -0
  27. dataforge/locales/en_CA/internet.py +34 -0
  28. dataforge/locales/en_CA/person.py +377 -0
  29. dataforge/locales/en_CA/phone.py +15 -0
  30. dataforge/locales/en_GB/__init__.py +1 -0
  31. dataforge/locales/en_GB/address.py +312 -0
  32. dataforge/locales/en_GB/company.py +196 -0
  33. dataforge/locales/en_GB/internet.py +34 -0
  34. dataforge/locales/en_GB/person.py +372 -0
  35. dataforge/locales/en_GB/phone.py +15 -0
  36. dataforge/locales/en_US/__init__.py +1 -0
  37. dataforge/locales/en_US/address.py +268 -0
  38. dataforge/locales/en_US/company.py +191 -0
  39. dataforge/locales/en_US/internet.py +34 -0
  40. dataforge/locales/en_US/person.py +370 -0
  41. dataforge/locales/en_US/phone.py +15 -0
  42. dataforge/locales/es_ES/__init__.py +1 -0
  43. dataforge/locales/es_ES/address.py +151 -0
  44. dataforge/locales/es_ES/company.py +125 -0
  45. dataforge/locales/es_ES/internet.py +30 -0
  46. dataforge/locales/es_ES/person.py +207 -0
  47. dataforge/locales/es_ES/phone.py +15 -0
  48. dataforge/locales/fr_FR/__init__.py +1 -0
  49. dataforge/locales/fr_FR/address.py +145 -0
  50. dataforge/locales/fr_FR/company.py +125 -0
  51. dataforge/locales/fr_FR/internet.py +30 -0
  52. dataforge/locales/fr_FR/person.py +212 -0
  53. dataforge/locales/fr_FR/phone.py +15 -0
  54. dataforge/locales/hi_IN/__init__.py +1 -0
  55. dataforge/locales/hi_IN/address.py +177 -0
  56. dataforge/locales/hi_IN/company.py +191 -0
  57. dataforge/locales/hi_IN/internet.py +26 -0
  58. dataforge/locales/hi_IN/person.py +218 -0
  59. dataforge/locales/hi_IN/phone.py +21 -0
  60. dataforge/locales/it_IT/__init__.py +1 -0
  61. dataforge/locales/it_IT/address.py +218 -0
  62. dataforge/locales/it_IT/company.py +151 -0
  63. dataforge/locales/it_IT/internet.py +31 -0
  64. dataforge/locales/it_IT/person.py +187 -0
  65. dataforge/locales/it_IT/phone.py +15 -0
  66. dataforge/locales/ja_JP/__init__.py +1 -0
  67. dataforge/locales/ja_JP/address.py +174 -0
  68. dataforge/locales/ja_JP/company.py +121 -0
  69. dataforge/locales/ja_JP/internet.py +30 -0
  70. dataforge/locales/ja_JP/person.py +207 -0
  71. dataforge/locales/ja_JP/phone.py +18 -0
  72. dataforge/locales/ko_KR/__init__.py +1 -0
  73. dataforge/locales/ko_KR/address.py +121 -0
  74. dataforge/locales/ko_KR/company.py +151 -0
  75. dataforge/locales/ko_KR/internet.py +30 -0
  76. dataforge/locales/ko_KR/person.py +157 -0
  77. dataforge/locales/ko_KR/phone.py +26 -0
  78. dataforge/locales/nl_NL/__init__.py +1 -0
  79. dataforge/locales/nl_NL/address.py +152 -0
  80. dataforge/locales/nl_NL/company.py +182 -0
  81. dataforge/locales/nl_NL/internet.py +41 -0
  82. dataforge/locales/nl_NL/person.py +218 -0
  83. dataforge/locales/nl_NL/phone.py +19 -0
  84. dataforge/locales/pl_PL/__init__.py +1 -0
  85. dataforge/locales/pl_PL/address.py +140 -0
  86. dataforge/locales/pl_PL/company.py +183 -0
  87. dataforge/locales/pl_PL/internet.py +36 -0
  88. dataforge/locales/pl_PL/person.py +217 -0
  89. dataforge/locales/pl_PL/phone.py +15 -0
  90. dataforge/locales/pt_BR/__init__.py +1 -0
  91. dataforge/locales/pt_BR/address.py +127 -0
  92. dataforge/locales/pt_BR/company.py +151 -0
  93. dataforge/locales/pt_BR/internet.py +31 -0
  94. dataforge/locales/pt_BR/person.py +187 -0
  95. dataforge/locales/pt_BR/phone.py +15 -0
  96. dataforge/locales/ru_RU/__init__.py +1 -0
  97. dataforge/locales/ru_RU/address.py +156 -0
  98. dataforge/locales/ru_RU/company.py +168 -0
  99. dataforge/locales/ru_RU/internet.py +26 -0
  100. dataforge/locales/ru_RU/person.py +218 -0
  101. dataforge/locales/ru_RU/phone.py +16 -0
  102. dataforge/locales/zh_CN/__init__.py +1 -0
  103. dataforge/locales/zh_CN/address.py +141 -0
  104. dataforge/locales/zh_CN/company.py +151 -0
  105. dataforge/locales/zh_CN/internet.py +30 -0
  106. dataforge/locales/zh_CN/person.py +157 -0
  107. dataforge/locales/zh_CN/phone.py +25 -0
  108. dataforge/providers/__init__.py +1 -0
  109. dataforge/providers/address.py +460 -0
  110. dataforge/providers/ai_chat.py +170 -0
  111. dataforge/providers/ai_prompt.py +447 -0
  112. dataforge/providers/automotive.py +416 -0
  113. dataforge/providers/barcode.py +149 -0
  114. dataforge/providers/base.py +34 -0
  115. dataforge/providers/color.py +247 -0
  116. dataforge/providers/company.py +144 -0
  117. dataforge/providers/crypto.py +105 -0
  118. dataforge/providers/datetime.py +397 -0
  119. dataforge/providers/ecommerce.py +316 -0
  120. dataforge/providers/education.py +234 -0
  121. dataforge/providers/file.py +271 -0
  122. dataforge/providers/finance.py +545 -0
  123. dataforge/providers/geo.py +332 -0
  124. dataforge/providers/government.py +114 -0
  125. dataforge/providers/internet.py +351 -0
  126. dataforge/providers/llm.py +726 -0
  127. dataforge/providers/lorem.py +241 -0
  128. dataforge/providers/medical.py +364 -0
  129. dataforge/providers/misc.py +196 -0
  130. dataforge/providers/network.py +283 -0
  131. dataforge/providers/payment.py +300 -0
  132. dataforge/providers/person.py +195 -0
  133. dataforge/providers/phone.py +87 -0
  134. dataforge/providers/profile.py +265 -0
  135. dataforge/providers/science.py +365 -0
  136. dataforge/providers/text.py +365 -0
  137. dataforge/py.typed +0 -0
  138. dataforge/pytest_plugin.py +80 -0
  139. dataforge/registry.py +164 -0
  140. dataforge/schema.py +772 -0
  141. dataforge/unique.py +171 -0
  142. dataforge_py-0.2.0.dist-info/METADATA +964 -0
  143. dataforge_py-0.2.0.dist-info/RECORD +145 -0
  144. dataforge_py-0.2.0.dist-info/WHEEL +4 -0
  145. dataforge_py-0.2.0.dist-info/entry_points.txt +35 -0
@@ -0,0 +1,265 @@
1
+ """ProfileProvider — generates coherent fake user profiles.
2
+
3
+ Each profile composes data from multiple providers (person, internet,
4
+ address, phone) to produce a consistent record where names, emails,
5
+ and usernames relate to each other.
6
+
7
+ Individual string fields are exposed in ``_field_map`` for Schema
8
+ compatibility. The compound ``profile()`` method returns a ``dict``
9
+ and is available only via direct API use.
10
+ """
11
+
12
+ from typing import TYPE_CHECKING, Literal, overload
13
+
14
+ from dataforge.backend import RandomEngine
15
+ from dataforge.providers.base import BaseProvider
16
+
17
+ if TYPE_CHECKING:
18
+ from dataforge.core import DataForge
19
+
20
+
21
+ class ProfileProvider(BaseProvider):
22
+ """Generates coherent fake user profiles.
23
+
24
+ Unlike other providers, ``ProfileProvider`` needs a reference to
25
+ the parent :class:`DataForge` instance so it can delegate to
26
+ ``person``, ``internet``, ``address``, and ``phone`` providers.
27
+
28
+ Parameters
29
+ ----------
30
+ engine : RandomEngine
31
+ The shared random engine instance.
32
+ forge : DataForge
33
+ The parent DataForge instance for cross-provider access.
34
+ """
35
+
36
+ __slots__ = ("_forge",)
37
+
38
+ _provider_name = "profile"
39
+ _locale_modules: tuple[str, ...] = ()
40
+ _needs_forge: bool = True
41
+ _field_map: dict[str, str] = {
42
+ "profile_first_name": "profile_first_name",
43
+ "profile_last_name": "profile_last_name",
44
+ "profile_email": "profile_email",
45
+ "profile_phone": "profile_phone",
46
+ "profile_city": "profile_city",
47
+ "profile_state": "profile_state",
48
+ "profile_zip_code": "profile_zip_code",
49
+ "profile_job_title": "profile_job_title",
50
+ }
51
+
52
+ def __init__(self, engine: RandomEngine, forge: "DataForge") -> None:
53
+ super().__init__(engine)
54
+ self._forge = forge
55
+
56
+ # ------------------------------------------------------------------
57
+ # Individual field methods (for _field_map / Schema compatibility)
58
+ # These delegate to sub-providers — values are independent per call.
59
+ # For coherent profiles, use profile() instead.
60
+ # ------------------------------------------------------------------
61
+
62
+ @overload
63
+ def profile_first_name(self) -> str: ...
64
+ @overload
65
+ def profile_first_name(self, count: Literal[1]) -> str: ...
66
+ @overload
67
+ def profile_first_name(self, count: int) -> str | list[str]: ...
68
+ def profile_first_name(self, count: int = 1) -> str | list[str]:
69
+ """Generate a first name (delegates to PersonProvider).
70
+
71
+ Parameters
72
+ ----------
73
+ count : int
74
+ Number of names to generate.
75
+
76
+ Returns
77
+ -------
78
+ str or list[str]
79
+ """
80
+ return self._forge.person.first_name(count)
81
+
82
+ @overload
83
+ def profile_last_name(self) -> str: ...
84
+ @overload
85
+ def profile_last_name(self, count: Literal[1]) -> str: ...
86
+ @overload
87
+ def profile_last_name(self, count: int) -> str | list[str]: ...
88
+ def profile_last_name(self, count: int = 1) -> str | list[str]:
89
+ """Generate a last name (delegates to PersonProvider).
90
+
91
+ Parameters
92
+ ----------
93
+ count : int
94
+ Number of names to generate.
95
+
96
+ Returns
97
+ -------
98
+ str or list[str]
99
+ """
100
+ return self._forge.person.last_name(count)
101
+
102
+ @overload
103
+ def profile_email(self) -> str: ...
104
+ @overload
105
+ def profile_email(self, count: Literal[1]) -> str: ...
106
+ @overload
107
+ def profile_email(self, count: int) -> str | list[str]: ...
108
+ def profile_email(self, count: int = 1) -> str | list[str]:
109
+ """Generate an email address (delegates to InternetProvider).
110
+
111
+ Parameters
112
+ ----------
113
+ count : int
114
+ Number of emails to generate.
115
+
116
+ Returns
117
+ -------
118
+ str or list[str]
119
+ """
120
+ return self._forge.internet.email(count)
121
+
122
+ @overload
123
+ def profile_phone(self) -> str: ...
124
+ @overload
125
+ def profile_phone(self, count: Literal[1]) -> str: ...
126
+ @overload
127
+ def profile_phone(self, count: int) -> str | list[str]: ...
128
+ def profile_phone(self, count: int = 1) -> str | list[str]:
129
+ """Generate a phone number (delegates to PhoneProvider).
130
+
131
+ Parameters
132
+ ----------
133
+ count : int
134
+ Number of phone numbers to generate.
135
+
136
+ Returns
137
+ -------
138
+ str or list[str]
139
+ """
140
+ return self._forge.phone.phone_number(count)
141
+
142
+ @overload
143
+ def profile_city(self) -> str: ...
144
+ @overload
145
+ def profile_city(self, count: Literal[1]) -> str: ...
146
+ @overload
147
+ def profile_city(self, count: int) -> str | list[str]: ...
148
+ def profile_city(self, count: int = 1) -> str | list[str]:
149
+ """Generate a city name (delegates to AddressProvider).
150
+
151
+ Parameters
152
+ ----------
153
+ count : int
154
+ Number of cities to generate.
155
+
156
+ Returns
157
+ -------
158
+ str or list[str]
159
+ """
160
+ return self._forge.address.city(count)
161
+
162
+ @overload
163
+ def profile_state(self) -> str: ...
164
+ @overload
165
+ def profile_state(self, count: Literal[1]) -> str: ...
166
+ @overload
167
+ def profile_state(self, count: int) -> str | list[str]: ...
168
+ def profile_state(self, count: int = 1) -> str | list[str]:
169
+ """Generate a state name (delegates to AddressProvider).
170
+
171
+ Parameters
172
+ ----------
173
+ count : int
174
+ Number of states to generate.
175
+
176
+ Returns
177
+ -------
178
+ str or list[str]
179
+ """
180
+ return self._forge.address.state(count)
181
+
182
+ @overload
183
+ def profile_zip_code(self) -> str: ...
184
+ @overload
185
+ def profile_zip_code(self, count: Literal[1]) -> str: ...
186
+ @overload
187
+ def profile_zip_code(self, count: int) -> str | list[str]: ...
188
+ def profile_zip_code(self, count: int = 1) -> str | list[str]:
189
+ """Generate a zip code (delegates to AddressProvider).
190
+
191
+ Parameters
192
+ ----------
193
+ count : int
194
+ Number of zip codes to generate.
195
+
196
+ Returns
197
+ -------
198
+ str or list[str]
199
+ """
200
+ return self._forge.address.zip_code(count)
201
+
202
+ @overload
203
+ def profile_job_title(self) -> str: ...
204
+ @overload
205
+ def profile_job_title(self, count: Literal[1]) -> str: ...
206
+ @overload
207
+ def profile_job_title(self, count: int) -> str | list[str]: ...
208
+ def profile_job_title(self, count: int = 1) -> str | list[str]:
209
+ """Generate a job title (delegates to CompanyProvider).
210
+
211
+ Parameters
212
+ ----------
213
+ count : int
214
+ Number of job titles to generate.
215
+
216
+ Returns
217
+ -------
218
+ str or list[str]
219
+ """
220
+ return self._forge.company.job_title(count)
221
+
222
+ # ------------------------------------------------------------------
223
+ # Compound profile method (direct API only, not in _field_map)
224
+ # ------------------------------------------------------------------
225
+
226
+ def profile(self, count: int = 1) -> dict[str, str] | list[dict[str, str]]:
227
+ """Generate a coherent user profile.
228
+
229
+ Each profile is a ``dict`` with keys: ``first_name``,
230
+ ``last_name``, ``email``, ``phone``, ``city``, ``state``,
231
+ ``zip_code``, ``job_title``.
232
+
233
+ The ``email`` is derived from the same first/last name for
234
+ coherence within each profile.
235
+
236
+ Parameters
237
+ ----------
238
+ count : int
239
+ Number of profiles to generate.
240
+
241
+ Returns
242
+ -------
243
+ dict[str, str] or list[dict[str, str]]
244
+ """
245
+
246
+ def _one_profile() -> dict[str, str]:
247
+ first = self._forge.person.first_name()
248
+ last = self._forge.person.last_name()
249
+ # Build a coherent email from the person's name
250
+ domain = self._forge.internet.domain()
251
+ email = f"{first.lower()}.{last.lower()}@{domain}"
252
+ return {
253
+ "first_name": first,
254
+ "last_name": last,
255
+ "email": email,
256
+ "phone": self._forge.phone.phone_number(),
257
+ "city": self._forge.address.city(),
258
+ "state": self._forge.address.state(),
259
+ "zip_code": self._forge.address.zip_code(),
260
+ "job_title": self._forge.company.job_title(),
261
+ }
262
+
263
+ if count == 1:
264
+ return _one_profile()
265
+ return [_one_profile() for _ in range(count)]
@@ -0,0 +1,365 @@
1
+ """Science provider — elements, units, formulas, planets, etc."""
2
+
3
+ from typing import Literal, overload
4
+
5
+ from dataforge.providers.base import BaseProvider
6
+
7
+ _CHEMICAL_ELEMENTS: tuple[str, ...] = (
8
+ "Hydrogen",
9
+ "Helium",
10
+ "Lithium",
11
+ "Beryllium",
12
+ "Boron",
13
+ "Carbon",
14
+ "Nitrogen",
15
+ "Oxygen",
16
+ "Fluorine",
17
+ "Neon",
18
+ "Sodium",
19
+ "Magnesium",
20
+ "Aluminum",
21
+ "Silicon",
22
+ "Phosphorus",
23
+ "Sulfur",
24
+ "Chlorine",
25
+ "Argon",
26
+ "Potassium",
27
+ "Calcium",
28
+ "Scandium",
29
+ "Titanium",
30
+ "Vanadium",
31
+ "Chromium",
32
+ "Manganese",
33
+ "Iron",
34
+ "Cobalt",
35
+ "Nickel",
36
+ "Copper",
37
+ "Zinc",
38
+ "Gallium",
39
+ "Germanium",
40
+ "Arsenic",
41
+ "Selenium",
42
+ "Bromine",
43
+ "Krypton",
44
+ "Rubidium",
45
+ "Strontium",
46
+ "Yttrium",
47
+ "Zirconium",
48
+ "Niobium",
49
+ "Molybdenum",
50
+ "Technetium",
51
+ "Ruthenium",
52
+ "Rhodium",
53
+ "Palladium",
54
+ "Silver",
55
+ "Gold",
56
+ "Platinum",
57
+ "Iridium",
58
+ )
59
+
60
+ _ELEMENT_SYMBOLS: tuple[str, ...] = (
61
+ "H",
62
+ "He",
63
+ "Li",
64
+ "Be",
65
+ "B",
66
+ "C",
67
+ "N",
68
+ "O",
69
+ "F",
70
+ "Ne",
71
+ "Na",
72
+ "Mg",
73
+ "Al",
74
+ "Si",
75
+ "P",
76
+ "S",
77
+ "Cl",
78
+ "Ar",
79
+ "K",
80
+ "Ca",
81
+ "Sc",
82
+ "Ti",
83
+ "V",
84
+ "Cr",
85
+ "Mn",
86
+ "Fe",
87
+ "Co",
88
+ "Ni",
89
+ "Cu",
90
+ "Zn",
91
+ "Ga",
92
+ "Ge",
93
+ "As",
94
+ "Se",
95
+ "Br",
96
+ "Kr",
97
+ "Rb",
98
+ "Sr",
99
+ "Y",
100
+ "Zr",
101
+ "Nb",
102
+ "Mo",
103
+ "Tc",
104
+ "Ru",
105
+ "Rh",
106
+ "Pd",
107
+ "Ag",
108
+ "Au",
109
+ "Pt",
110
+ "Ir",
111
+ )
112
+
113
+ _SI_UNITS: tuple[str, ...] = (
114
+ "meter (m)",
115
+ "kilogram (kg)",
116
+ "second (s)",
117
+ "ampere (A)",
118
+ "kelvin (K)",
119
+ "mole (mol)",
120
+ "candela (cd)",
121
+ "hertz (Hz)",
122
+ "newton (N)",
123
+ "pascal (Pa)",
124
+ "joule (J)",
125
+ "watt (W)",
126
+ "coulomb (C)",
127
+ "volt (V)",
128
+ "farad (F)",
129
+ "ohm (Ω)",
130
+ "siemens (S)",
131
+ "weber (Wb)",
132
+ "tesla (T)",
133
+ "henry (H)",
134
+ "lumen (lm)",
135
+ "lux (lx)",
136
+ "becquerel (Bq)",
137
+ "gray (Gy)",
138
+ "sievert (Sv)",
139
+ )
140
+
141
+ _PLANETS: tuple[str, ...] = (
142
+ "Mercury",
143
+ "Venus",
144
+ "Earth",
145
+ "Mars",
146
+ "Jupiter",
147
+ "Saturn",
148
+ "Uranus",
149
+ "Neptune",
150
+ )
151
+
152
+ _GALAXIES: tuple[str, ...] = (
153
+ "Milky Way",
154
+ "Andromeda",
155
+ "Triangulum",
156
+ "Centaurus A",
157
+ "Messier 87",
158
+ "Sombrero Galaxy",
159
+ "Whirlpool Galaxy",
160
+ "Pinwheel Galaxy",
161
+ "Cartwheel Galaxy",
162
+ "Cigar Galaxy",
163
+ "Black Eye Galaxy",
164
+ "Sunflower Galaxy",
165
+ "Tadpole Galaxy",
166
+ "Hoag's Object",
167
+ "NGC 1300",
168
+ )
169
+
170
+ _CONSTELLATIONS: tuple[str, ...] = (
171
+ "Orion",
172
+ "Ursa Major",
173
+ "Ursa Minor",
174
+ "Cassiopeia",
175
+ "Scorpius",
176
+ "Leo",
177
+ "Gemini",
178
+ "Taurus",
179
+ "Sagittarius",
180
+ "Andromeda",
181
+ "Aquarius",
182
+ "Aries",
183
+ "Cancer",
184
+ "Capricornus",
185
+ "Libra",
186
+ "Pisces",
187
+ "Virgo",
188
+ "Cygnus",
189
+ "Lyra",
190
+ "Pegasus",
191
+ "Perseus",
192
+ "Draco",
193
+ "Centaurus",
194
+ "Canis Major",
195
+ "Canis Minor",
196
+ )
197
+
198
+ _SCIENTIFIC_DISCIPLINES: tuple[str, ...] = (
199
+ "Physics",
200
+ "Chemistry",
201
+ "Biology",
202
+ "Mathematics",
203
+ "Astronomy",
204
+ "Geology",
205
+ "Ecology",
206
+ "Genetics",
207
+ "Neuroscience",
208
+ "Biochemistry",
209
+ "Quantum Mechanics",
210
+ "Thermodynamics",
211
+ "Electrodynamics",
212
+ "Organic Chemistry",
213
+ "Inorganic Chemistry",
214
+ "Molecular Biology",
215
+ "Astrophysics",
216
+ "Cosmology",
217
+ "Paleontology",
218
+ "Oceanography",
219
+ "Meteorology",
220
+ "Seismology",
221
+ "Virology",
222
+ "Immunology",
223
+ "Epidemiology",
224
+ )
225
+
226
+ _METRIC_PREFIXES: tuple[str, ...] = (
227
+ "yotta (Y)",
228
+ "zetta (Z)",
229
+ "exa (E)",
230
+ "peta (P)",
231
+ "tera (T)",
232
+ "giga (G)",
233
+ "mega (M)",
234
+ "kilo (k)",
235
+ "hecto (h)",
236
+ "deca (da)",
237
+ "deci (d)",
238
+ "centi (c)",
239
+ "milli (m)",
240
+ "micro (μ)",
241
+ "nano (n)",
242
+ "pico (p)",
243
+ "femto (f)",
244
+ "atto (a)",
245
+ )
246
+
247
+
248
+ class ScienceProvider(BaseProvider):
249
+ """Generates fake science data — elements, planets, units, etc."""
250
+
251
+ __slots__ = ()
252
+
253
+ _provider_name = "science"
254
+ _locale_modules: tuple[str, ...] = ()
255
+ _field_map: dict[str, str] = {
256
+ "chemical_element": "chemical_element",
257
+ "element": "chemical_element",
258
+ "element_symbol": "element_symbol",
259
+ "si_unit": "si_unit",
260
+ "unit": "si_unit",
261
+ "planet": "planet",
262
+ "galaxy": "galaxy",
263
+ "constellation": "constellation",
264
+ "scientific_discipline": "scientific_discipline",
265
+ "discipline": "scientific_discipline",
266
+ "metric_prefix": "metric_prefix",
267
+ }
268
+
269
+ # --- Public API ---
270
+
271
+ @overload
272
+ def chemical_element(self) -> str: ...
273
+ @overload
274
+ def chemical_element(self, count: Literal[1]) -> str: ...
275
+ @overload
276
+ def chemical_element(self, count: int) -> str | list[str]: ...
277
+ def chemical_element(self, count: int = 1) -> str | list[str]:
278
+ """Generate a chemical element name (e.g., Hydrogen, Carbon)."""
279
+ if count == 1:
280
+ return self._engine.choice(_CHEMICAL_ELEMENTS)
281
+ return self._engine.choices(_CHEMICAL_ELEMENTS, count)
282
+
283
+ @overload
284
+ def element_symbol(self) -> str: ...
285
+ @overload
286
+ def element_symbol(self, count: Literal[1]) -> str: ...
287
+ @overload
288
+ def element_symbol(self, count: int) -> str | list[str]: ...
289
+ def element_symbol(self, count: int = 1) -> str | list[str]:
290
+ """Generate a chemical element symbol (e.g., H, C, Fe)."""
291
+ if count == 1:
292
+ return self._engine.choice(_ELEMENT_SYMBOLS)
293
+ return self._engine.choices(_ELEMENT_SYMBOLS, count)
294
+
295
+ @overload
296
+ def si_unit(self) -> str: ...
297
+ @overload
298
+ def si_unit(self, count: Literal[1]) -> str: ...
299
+ @overload
300
+ def si_unit(self, count: int) -> str | list[str]: ...
301
+ def si_unit(self, count: int = 1) -> str | list[str]:
302
+ """Generate an SI unit (e.g., meter (m), joule (J))."""
303
+ if count == 1:
304
+ return self._engine.choice(_SI_UNITS)
305
+ return self._engine.choices(_SI_UNITS, count)
306
+
307
+ @overload
308
+ def planet(self) -> str: ...
309
+ @overload
310
+ def planet(self, count: Literal[1]) -> str: ...
311
+ @overload
312
+ def planet(self, count: int) -> str | list[str]: ...
313
+ def planet(self, count: int = 1) -> str | list[str]:
314
+ """Generate a planet name from our solar system."""
315
+ if count == 1:
316
+ return self._engine.choice(_PLANETS)
317
+ return self._engine.choices(_PLANETS, count)
318
+
319
+ @overload
320
+ def galaxy(self) -> str: ...
321
+ @overload
322
+ def galaxy(self, count: Literal[1]) -> str: ...
323
+ @overload
324
+ def galaxy(self, count: int) -> str | list[str]: ...
325
+ def galaxy(self, count: int = 1) -> str | list[str]:
326
+ """Generate a galaxy name (e.g., Milky Way, Andromeda)."""
327
+ if count == 1:
328
+ return self._engine.choice(_GALAXIES)
329
+ return self._engine.choices(_GALAXIES, count)
330
+
331
+ @overload
332
+ def constellation(self) -> str: ...
333
+ @overload
334
+ def constellation(self, count: Literal[1]) -> str: ...
335
+ @overload
336
+ def constellation(self, count: int) -> str | list[str]: ...
337
+ def constellation(self, count: int = 1) -> str | list[str]:
338
+ """Generate a constellation name (e.g., Orion, Leo)."""
339
+ if count == 1:
340
+ return self._engine.choice(_CONSTELLATIONS)
341
+ return self._engine.choices(_CONSTELLATIONS, count)
342
+
343
+ @overload
344
+ def scientific_discipline(self) -> str: ...
345
+ @overload
346
+ def scientific_discipline(self, count: Literal[1]) -> str: ...
347
+ @overload
348
+ def scientific_discipline(self, count: int) -> str | list[str]: ...
349
+ def scientific_discipline(self, count: int = 1) -> str | list[str]:
350
+ """Generate a scientific discipline (e.g., Physics, Genetics)."""
351
+ if count == 1:
352
+ return self._engine.choice(_SCIENTIFIC_DISCIPLINES)
353
+ return self._engine.choices(_SCIENTIFIC_DISCIPLINES, count)
354
+
355
+ @overload
356
+ def metric_prefix(self) -> str: ...
357
+ @overload
358
+ def metric_prefix(self, count: Literal[1]) -> str: ...
359
+ @overload
360
+ def metric_prefix(self, count: int) -> str | list[str]: ...
361
+ def metric_prefix(self, count: int = 1) -> str | list[str]:
362
+ """Generate a metric prefix (e.g., kilo (k), nano (n))."""
363
+ if count == 1:
364
+ return self._engine.choice(_METRIC_PREFIXES)
365
+ return self._engine.choices(_METRIC_PREFIXES, count)