pandahub 0.3.16__zip → 0.4.0__zip

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 (52) hide show
  1. {pandahub-0.3.16 → pandahub-0.4.0}/CHANGELOG.md +3 -0
  2. {pandahub-0.3.16 → pandahub-0.4.0}/PKG-INFO +44 -2
  3. pandahub-0.4.0/README.md +58 -0
  4. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/internal/settings.py +1 -0
  5. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/lib/PandaHub.py +54 -31
  6. pandahub-0.4.0/pandahub/lib/__init__.py +1 -0
  7. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/lib/database_toolbox.py +0 -55
  8. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/lib/datatypes.py +4 -10
  9. pandahub-0.4.0/pandahub/lib/mongo_client.py +78 -0
  10. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub.egg-info/PKG-INFO +44 -2
  11. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub.egg-info/SOURCES.txt +1 -0
  12. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub.egg-info/requires.txt +2 -2
  13. {pandahub-0.3.16 → pandahub-0.4.0}/pyproject.toml +1 -1
  14. {pandahub-0.3.16 → pandahub-0.4.0}/requirements.txt +2 -2
  15. pandahub-0.3.16/README.md +0 -16
  16. pandahub-0.3.16/pandahub/lib/timeseries/data_sources/__init__.py +0 -0
  17. {pandahub-0.3.16 → pandahub-0.4.0}/CONTRIBUTING.rst +0 -0
  18. {pandahub-0.3.16 → pandahub-0.4.0}/LICENSE +0 -0
  19. {pandahub-0.3.16 → pandahub-0.4.0}/MANIFEST.in +0 -0
  20. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/__init__.py +0 -0
  21. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/__init__.py +0 -0
  22. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/dependencies.py +0 -0
  23. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/internal/__init__.py +0 -0
  24. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/internal/db.py +0 -0
  25. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/internal/schemas.py +0 -0
  26. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/internal/toolbox.py +0 -0
  27. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/internal/users.py +0 -0
  28. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/main.py +0 -0
  29. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/routers/__init__.py +0 -0
  30. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/routers/auth.py +0 -0
  31. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/routers/net.py +0 -0
  32. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/routers/projects.py +0 -0
  33. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/routers/timeseries.py +0 -0
  34. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/routers/users.py +0 -0
  35. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/api/routers/variants.py +0 -0
  36. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/lib/mongodb_indexes.py +0 -0
  37. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/lib/timeseries/__init__.py +0 -0
  38. {pandahub-0.3.16/pandahub/lib → pandahub-0.4.0/pandahub/lib/timeseries/data_sources}/__init__.py +0 -0
  39. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/lib/timeseries/data_sources/mongo_data.py +0 -0
  40. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub/lib/timeseries/output_writer_mongodb.py +0 -0
  41. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub.egg-info/dependency_links.txt +0 -0
  42. {pandahub-0.3.16 → pandahub-0.4.0}/pandahub.egg-info/top_level.txt +0 -0
  43. {pandahub-0.3.16 → pandahub-0.4.0}/setup.cfg +0 -0
  44. {pandahub-0.3.16 → pandahub-0.4.0}/setup.py +0 -0
  45. {pandahub-0.3.16 → pandahub-0.4.0}/test/__init__.py +0 -0
  46. {pandahub-0.3.16 → pandahub-0.4.0}/test/conftest.py +0 -0
  47. {pandahub-0.3.16 → pandahub-0.4.0}/test/performance_test.py +0 -0
  48. {pandahub-0.3.16 → pandahub-0.4.0}/test/test_client.py +0 -0
  49. {pandahub-0.3.16 → pandahub-0.4.0}/test/test_networks.py +0 -0
  50. {pandahub-0.3.16 → pandahub-0.4.0}/test/test_projects.py +0 -0
  51. {pandahub-0.3.16 → pandahub-0.4.0}/test/test_timeseries.py +0 -0
  52. {pandahub-0.3.16 → pandahub-0.4.0}/tutorials/config.py +0 -0
@@ -1,5 +1,8 @@
1
1
  # Change Log
2
2
 
3
+ ## [0.4.0]
4
+ - IMPROVED: pandapower 3.0 support
5
+
3
6
  ## [0.3.13]
4
7
  - FIXED: fetching integer index on empty collection
5
8
  - IMPROVED: context manager for mongo client /database / collection
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pandahub
3
- Version: 0.3.16
3
+ Version: 0.4.0
4
4
  Summary: Data hub for pandapower and pandapipes networks based on MongoDB
5
5
  Author-email: Jan Ulffers <jan.ulffers@iee.fraunhofer.de>, Leon Thurner <leon.thurner@retoflow.de>, Jannis Kupka <jannis.kupka@retoflow.de>, Mike Vogt <mike.vogt@iee.fraunhofer.de>, Joschka Thurner <joschka.thurner@retoflow.de>, Alexander Scheidler <alexander.scheidler@iee.fraunhofer.de>
6
6
  License: Copyright (c) 2022 by University of Kassel, Fraunhofer Institute for Energy Economics
@@ -59,7 +59,7 @@ Provides-Extra: test
59
59
  Provides-Extra: all
60
60
  License-File: LICENSE
61
61
 
62
- [![pandapower](https://www.pandapower.org/images/pp.svg)](https://www.pandapower.org) [![pandapipes](https://www.pandapipes.org/images/pp.svg)](https://www.pandapipes.org)
62
+ from lib import get_mongo_client[![pandapower](https://www.pandapower.org/images/pp.svg)](https://www.pandapower.org) [![pandapipes](https://www.pandapipes.org/images/pp.svg)](https://www.pandapipes.org)
63
63
 
64
64
  [![pandahub](https://badge.fury.io/py/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/pypi/pyversions/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/e2nIEE/pandahub/blob/master/LICENSE)
65
65
 
@@ -75,3 +75,45 @@ Swagger UI is available at http://localhost:8002/docs.
75
75
 
76
76
  If you develop on the library and do not need the fastapi app, `docker compose up db -d` starts only the mongodb
77
77
  container.
78
+
79
+ ## MongoClient handling
80
+
81
+ You can supply a mongodb connection string (or the host as connection string and user/password separately) either as arguments to PandaHub
82
+ or by setting the following environment variables:
83
+
84
+ * MONGODB_URL
85
+ * MONGODB_USER (optional)
86
+ * MONGODB_PASSWORD (optional)
87
+
88
+ These values are then used as default arguments when creating a PandaHub object.
89
+
90
+ By default, each instantiation of a PandaHub instance will create a new MongoClient instance. This is fine for interactive use or small scripts. To clean up the mongo client, call `ph.close()` on the instance once you are done with it.
91
+
92
+ When running the pandahub API as a web application, provide database credentials through the environment variables and additionally set `PANDAHUB_GLOBAL_DB_CLIENT` to `true`.
93
+ A single, global mongodb client instance per process will then be used for all PandaHub instantiations, as recommended by [pymongo](https://pymongo.readthedocs.io/en/stable/faq.html#how-does-connection-pooling-work-in-pymongo).
94
+
95
+ You can use the following properties to access database resources from a PandaHub object:
96
+
97
+ * *ph.project_db*: database of the activated project (e.g. `ph.project_db.net_bus.find().to_list()`)
98
+ * *ph.mgmt_db*: the user_management database
99
+ * *ph.users_collection*: the users collection from the management database
100
+ * *ph.projects_collection*: the projects collection from the management database
101
+
102
+
103
+ In case you want to access the database but do not need a PandaHub object, there are two convenience functions available:
104
+
105
+ ```python
106
+ from pandahub.lib import get_mongo_client
107
+
108
+ my_collection = get_mongo_client("db", "collection")
109
+ docs = collection.find().to_list()
110
+ ```
111
+ `PANDAHUB_GLOBAL_DB_CLIENT` is respected, you either get the global client or a fresh instance, in which case you are responsible for cleaning up the connection.
112
+
113
+ ```python
114
+ from pandahub.lib import mongo_client
115
+
116
+ with mongo_client("db", "my_collection") as collection:
117
+ docs = collection.find().to_list()
118
+ ```
119
+ With the context manager, a new MongoClient is created (even if `PANDAHUB_GLOBAL_DB_CLIENT` is `true`) and closed on completion of the with block.
@@ -0,0 +1,58 @@
1
+ from lib import get_mongo_client[![pandapower](https://www.pandapower.org/images/pp.svg)](https://www.pandapower.org) [![pandapipes](https://www.pandapipes.org/images/pp.svg)](https://www.pandapipes.org)
2
+
3
+ [![pandahub](https://badge.fury.io/py/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/pypi/pyversions/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/e2nIEE/pandahub/blob/master/LICENSE)
4
+
5
+ pandahub is a data hub for pandapower and pandapipes networks based on MongoDB. It allows you to store pandapower and
6
+ pandapipes networks as well as timeseries in a MongoDB. pandahub allows you to access the database directly through the PandaHub class,
7
+ but also provides a REST-API based on FastAPI. Access through the API is managed with a user management implementation based on FastAPI Users.
8
+
9
+ ## Development
10
+ `docker compose up -d` runs a mongodb container alongside a pandahub api instance with live reload available
11
+ at http://localhost:8002. To connect to an existing database instead, set `MONGODB_URL` to the connection string through an environment variable / in you `.env` file.
12
+
13
+ Swagger UI is available at http://localhost:8002/docs.
14
+
15
+ If you develop on the library and do not need the fastapi app, `docker compose up db -d` starts only the mongodb
16
+ container.
17
+
18
+ ## MongoClient handling
19
+
20
+ You can supply a mongodb connection string (or the host as connection string and user/password separately) either as arguments to PandaHub
21
+ or by setting the following environment variables:
22
+
23
+ * MONGODB_URL
24
+ * MONGODB_USER (optional)
25
+ * MONGODB_PASSWORD (optional)
26
+
27
+ These values are then used as default arguments when creating a PandaHub object.
28
+
29
+ By default, each instantiation of a PandaHub instance will create a new MongoClient instance. This is fine for interactive use or small scripts. To clean up the mongo client, call `ph.close()` on the instance once you are done with it.
30
+
31
+ When running the pandahub API as a web application, provide database credentials through the environment variables and additionally set `PANDAHUB_GLOBAL_DB_CLIENT` to `true`.
32
+ A single, global mongodb client instance per process will then be used for all PandaHub instantiations, as recommended by [pymongo](https://pymongo.readthedocs.io/en/stable/faq.html#how-does-connection-pooling-work-in-pymongo).
33
+
34
+ You can use the following properties to access database resources from a PandaHub object:
35
+
36
+ * *ph.project_db*: database of the activated project (e.g. `ph.project_db.net_bus.find().to_list()`)
37
+ * *ph.mgmt_db*: the user_management database
38
+ * *ph.users_collection*: the users collection from the management database
39
+ * *ph.projects_collection*: the projects collection from the management database
40
+
41
+
42
+ In case you want to access the database but do not need a PandaHub object, there are two convenience functions available:
43
+
44
+ ```python
45
+ from pandahub.lib import get_mongo_client
46
+
47
+ my_collection = get_mongo_client("db", "collection")
48
+ docs = collection.find().to_list()
49
+ ```
50
+ `PANDAHUB_GLOBAL_DB_CLIENT` is respected, you either get the global client or a fresh instance, in which case you are responsible for cleaning up the connection.
51
+
52
+ ```python
53
+ from pandahub.lib import mongo_client
54
+
55
+ with mongo_client("db", "my_collection") as collection:
56
+ docs = collection.find().to_list()
57
+ ```
58
+ With the context manager, a new MongoClient is created (even if `PANDAHUB_GLOBAL_DB_CLIENT` is `true`) and closed on completion of the with block.
@@ -58,3 +58,4 @@ DEBUG = settings_bool("DEBUG", default=False)
58
58
  PANDAHUB_SERVER_URL = get_os_env("PANDAHUB_SERVER_URL", "0.0.0.0")
59
59
  PANDAHUB_SERVER_PORT = int(get_os_env('PANDAHUB_SERVER_PORT', 8002))
60
60
  WORKERS = int(get_os_env('WORKER', 2))
61
+ PANDAHUB_GLOBAL_DB_CLIENT = settings_bool("PANDAHUB_GLOBAL_DB_CLIENT", default=False)
@@ -27,19 +27,19 @@ import pandapower as pp
27
27
  import pandapower.io_utils as io_pp
28
28
 
29
29
  from pandahub.api.internal.settings import MONGODB_URL, MONGODB_USER, MONGODB_PASSWORD, MONGODB_GLOBAL_DATABASE_URL, \
30
- MONGODB_GLOBAL_DATABASE_USER, MONGODB_GLOBAL_DATABASE_PASSWORD, CREATE_INDEXES_WITH_PROJECT
30
+ MONGODB_GLOBAL_DATABASE_USER, MONGODB_GLOBAL_DATABASE_PASSWORD, CREATE_INDEXES_WITH_PROJECT, PANDAHUB_GLOBAL_DB_CLIENT
31
31
  from pandahub.lib.database_toolbox import (
32
32
  create_timeseries_document,
33
33
  convert_timeseries_to_subdocuments,
34
34
  convert_element_to_dict,
35
- get_mongo_client,
36
35
  json_to_object,
37
36
  serialize_object_data,
38
37
  get_dtypes,
39
38
  decompress_timeseries_data,
40
39
  convert_geojsons,
41
- get_metadata_for_timeseries_collections
40
+ get_metadata_for_timeseries_collections,
42
41
  )
42
+ from pandahub.lib import get_mongo_client
43
43
  from pandahub.lib.mongodb_indexes import MONGODB_INDEXES
44
44
 
45
45
  logger = logging.getLogger(__name__)
@@ -102,7 +102,11 @@ class PandaHub:
102
102
  ):
103
103
  self._datatypes = datatypes
104
104
  self.mongodb_indexes = mongodb_indexes
105
- self.mongo_client = get_mongo_client(connection_url, connection_user, connection_password)
105
+ self.mongo_client = get_mongo_client(
106
+ connection_url=connection_url,
107
+ connection_user=connection_user,
108
+ connection_password=connection_password,
109
+ )
106
110
  self.mongo_client_global_db = None
107
111
  self.active_project = None
108
112
  self.user_id = user_id
@@ -117,6 +121,27 @@ class PandaHub:
117
121
  if check_server_available:
118
122
  self.server_is_available()
119
123
 
124
+
125
+ @property
126
+ def project_db(self) -> Database:
127
+ """Get the database of the current active project"""
128
+ return self.get_project_database()
129
+
130
+ @property
131
+ def mgmt_db(self) -> Database:
132
+ """Get the user_management database"""
133
+ return self.mongo_client["user_management"]
134
+
135
+ @property
136
+ def users_collection(self) -> Collection:
137
+ """Get the users collection"""
138
+ return self.mongo_client["user_management"]["users"]
139
+
140
+ @property
141
+ def projects_collection(self) -> Collection:
142
+ """Get the projects collection"""
143
+ return self.mongo_client["user_management"]["projects"]
144
+
120
145
  # -------------------------
121
146
  # Database connection checks
122
147
  # -------------------------
@@ -150,6 +175,12 @@ class PandaHub:
150
175
  except ServerSelectionTimeoutError as e:
151
176
  return "connection timeout"
152
177
 
178
+ def close(self, force=False):
179
+ """Close the database connection."""
180
+ if PANDAHUB_GLOBAL_DB_CLIENT and not force:
181
+ msg = "Closing the global MongoClient instance will break all subsequent database connections. If this is intentional, use force=True"
182
+ raise PandaHubError(msg)
183
+ self.mongo_client.close()
153
184
  # -------------------------
154
185
  # Permission check
155
186
  # -------------------------
@@ -272,7 +303,7 @@ class PandaHub:
272
303
  if realm is not None:
273
304
  filter_dict["realm"] = realm
274
305
  db = self.mongo_client["user_management"]
275
- projects = list(db["projects"].find(filter_dict))
306
+ projects = db["projects"].find(filter_dict).to_list()
276
307
  return [
277
308
  {
278
309
  "id": str(p["_id"]),
@@ -401,8 +432,7 @@ class PandaHub:
401
432
  return project is not None
402
433
 
403
434
  def _get_project_document(self, filter_dict: dict) -> Optional[dict]:
404
- project_collection = self.mongo_client["user_management"].projects
405
- projects = list(project_collection.find(filter_dict))
435
+ projects = self.projects_collection.find(filter_dict).to_list()
406
436
  if len(projects) == 0: # project doesn't exist
407
437
  return None
408
438
  if len(projects) > 1:
@@ -437,8 +467,12 @@ class PandaHub:
437
467
  -------
438
468
  pymongo.database.Database
439
469
  """
470
+ if self.active_project is None:
471
+ msg = "Can not access project database - no project active!"
472
+ raise PandaHubError(msg)
473
+ project_id = str(self.active_project["_id"])
440
474
  if collection is None:
441
- return self.mongo_client[str(self.active_project["_id"])]
475
+ return self.mongo_client[project_id]
442
476
  logger.warning(f"Passing a collection to get_project_database is deprecated. Use get_project_collection instead!")
443
477
  return self.get_project_collection(collection)
444
478
 
@@ -487,8 +521,6 @@ class PandaHub:
487
521
  list
488
522
  network ids
489
523
  """
490
- if not self.active_project:
491
- raise PandaHubError("No project activated!")
492
524
  return self.get_project_collection("_networks").find({}, {"_id:": 1}).distinct("_id")
493
525
 
494
526
  def get_project_version(self):
@@ -503,7 +535,7 @@ class PandaHub:
503
535
  return
504
536
 
505
537
  if version.parse(self.get_project_version()) < version.parse("0.2.3"):
506
- db = self._get_project_database()
538
+ db = self.project_db
507
539
  all_collection_names = db.list_collection_names()
508
540
  old_net_collections = [
509
541
  name
@@ -516,9 +548,7 @@ class PandaHub:
516
548
  for element in old_net_collections:
517
549
  db[element].rename(self._collection_name_of_element(element))
518
550
  # for all networks
519
- for d in list(
520
- db["_networks"].find({}, projection={"sector": 1, "data": 1})
521
- ):
551
+ for d in db["_networks"].find({}, projection={"sector": 1, "data": 1}).to_list():
522
552
  # load old format
523
553
  if d.get("sector", "power") == "power":
524
554
  data = dict(
@@ -729,7 +759,7 @@ class PandaHub:
729
759
  self.set_active_project_by_id(project_id)
730
760
  self.check_permission("read")
731
761
  db = self._get_project_database()
732
- return list(db["_networks"].find())
762
+ return db["_networks"].find().to_list()
733
763
 
734
764
 
735
765
  def get_network_by_name(
@@ -1241,11 +1271,11 @@ class PandaHub:
1241
1271
  proj = {"net": 0}
1242
1272
  if not load_area:
1243
1273
  proj["area_geojson"] = 0
1244
- nets = pd.DataFrame(list(db.find(fi, projection=proj)))
1274
+ nets = pd.DataFrame(db.find(fi, projection=proj)).to_list()
1245
1275
  return nets
1246
1276
 
1247
1277
  def _get_net_id_from_name(self, name, db):
1248
- metadata = list(db["_networks"].find({"name": name}))
1278
+ metadata = db["_networks"].find({"name": name}).to_list()
1249
1279
  if len(metadata) > 1:
1250
1280
  msg = (f"Multiple networks with the name {metadata[0]['name']} found in the database. "
1251
1281
  f"Use the corresponding function which selects the network by net_id instead.")
@@ -1302,9 +1332,7 @@ class PandaHub:
1302
1332
  else:
1303
1333
  filter_dict = {**filter_dict, **filter}
1304
1334
 
1305
- data = list(
1306
- db[self._collection_name_of_element(element_type)].find(filter_dict)
1307
- )
1335
+ data = db[self._collection_name_of_element(element_type)].find(filter_dict).to_list()
1308
1336
  if len(data) == 0:
1309
1337
  return
1310
1338
  if dtypes is None:
@@ -1344,17 +1372,12 @@ class PandaHub:
1344
1372
  if project_id:
1345
1373
  self.set_active_project_by_id(project_id)
1346
1374
  self.check_permission("write")
1347
- db = self._get_project_database()
1348
1375
 
1349
1376
  collection = self._collection_name_of_element(element_type)
1350
1377
  dtypes = self._datatypes.get(element_type)
1351
1378
 
1352
1379
  variant_filter = self.get_variant_filter(variant)
1353
- documents = list(
1354
- db[collection].find(
1355
- {"index": element_index, "net_id": net_id, **variant_filter}
1356
- )
1357
- )
1380
+ documents = self.projects_collection[collection].find({"index": element_index, "net_id": net_id, **variant_filter}).to_list()
1358
1381
  if len(documents) == 1:
1359
1382
  document = documents[0]
1360
1383
  else:
@@ -1446,7 +1469,7 @@ class PandaHub:
1446
1469
  **self.get_variant_filter(variant),
1447
1470
  }
1448
1471
 
1449
- deletion_targets = list(db[collection].find(element_filter))
1472
+ deletion_targets = db[collection].find(element_filter).to_list()
1450
1473
  if not deletion_targets:
1451
1474
  return []
1452
1475
 
@@ -1554,7 +1577,7 @@ class PandaHub:
1554
1577
  value = dtypes[parameter](value)
1555
1578
  collection = self._collection_name_of_element(element_type)
1556
1579
 
1557
- js = list(db[collection].find({"index": element_index, "net_id": net_id}))[0]
1580
+ js = db[collection].find({"index": element_index, "net_id": net_id}).to_list(1)
1558
1581
  obj = json_to_object(js["object"])
1559
1582
  setattr(obj, parameter, value)
1560
1583
  db[collection].update_one(
@@ -2419,7 +2442,7 @@ class PandaHub:
2419
2442
  if not include_metadata:
2420
2443
  pipeline.append({"$project": {"timeseries_data": 1,
2421
2444
  "num_timestamps": 1}})
2422
- data = list(db[collection_name].aggregate(pipeline))
2445
+ data = db[collection_name].aggregate(pipeline).to_list()
2423
2446
  if len(data) == 0:
2424
2447
  raise PandaHubError("no documents matching the provided filter found", 404)
2425
2448
  elif len(data) > 1:
@@ -2532,7 +2555,7 @@ class PandaHub:
2532
2555
  pipeline.append({"$match": {"$and": match_filter}})
2533
2556
  projection = {"$project": {"timeseries_data": 0}}
2534
2557
  pipeline.append(projection)
2535
- metadata = list(db[collection_name].aggregate(pipeline))
2558
+ metadata = db[collection_name].aggregate(pipeline).to_list()
2536
2559
  df_metadata = pd.DataFrame(metadata)
2537
2560
  if len(df_metadata):
2538
2561
  df_metadata.set_index("_id", inplace=True)
@@ -3086,7 +3109,7 @@ class PandaHub:
3086
3109
  global_database=False,
3087
3110
  ):
3088
3111
  db = self._get_project_or_global_db(project_id, global_database)
3089
- collections = list(db.list_collections(filter={"name": collection_name}))
3112
+ collections = db.list_collections(filter={"name": collection_name}).to_list()
3090
3113
  return len(collections) == 1 and collections[0]["type"] == "timeseries"
3091
3114
 
3092
3115
  def _get_project_or_global_db(self, project_id=None, global_database=False):
@@ -0,0 +1 @@
1
+ from pandahub.lib.mongo_client import mongo_client, get_mongo_client
@@ -1,14 +1,8 @@
1
1
  # -*- coding: utf-8 -*-
2
- from contextlib import contextmanager
3
- from collections.abc import Iterator
4
2
 
5
3
  import numpy as np
6
4
  import pandas as pd
7
- from pymongo import MongoClient
8
- from pymongo.database import Database
9
- from pymongo.collection import Collection
10
5
 
11
- from pandahub.api.internal.settings import MONGODB_URL, MONGODB_USER, MONGODB_PASSWORD
12
6
  from pandahub.lib.datatypes import DATATYPES
13
7
  import base64
14
8
  import hashlib
@@ -428,55 +422,6 @@ def migrate_userdb_to_beanie(ph):
428
422
  {'$out': 'users'}]
429
423
  userdb.aggregate(migration)
430
424
 
431
- def get_mongo_client(connection_url: str = MONGODB_URL, connection_user: str = MONGODB_USER,
432
- connection_password: str = MONGODB_PASSWORD) -> MongoClient:
433
- mongo_client_args = {
434
- "host": connection_url,
435
- "uuidRepresentation": "standard",
436
- "connect": False,
437
- }
438
- if connection_user:
439
- mongo_client_args |= {
440
- "username": connection_user,
441
- "password": connection_password,
442
- }
443
- return MongoClient(**mongo_client_args)
444
-
445
- @contextmanager
446
- def mongo_client(database: str | None = None, collection: str | None = None, connection_url: str = MONGODB_URL,
447
- connection_user: str = MONGODB_USER, connection_password: str = MONGODB_PASSWORD) -> Iterator[MongoClient | Database | Collection]:
448
- """Contextmanager for pymongo MongoClient / Database / Collection with close after use.
449
-
450
- Parameters
451
- ----------
452
- database: str or None
453
- The database to connect to
454
- collection: str or None
455
- The collection to connect to
456
- connection_url: str or None
457
- Defaults to MONGODB_URL env var
458
- connection_user: str or None
459
- Defaults to MONGODB_USER env var
460
- connection_password: str or None
461
- Defaults to MONGODB_PASSWORD env var
462
-
463
- Returns
464
- -------
465
- Contextmanager yielding MongoClient / Database / Collection
466
- """
467
- if collection is not None and database is None:
468
- raise ValueError("Must specify database to access a collection!")
469
- client = get_mongo_client(connection_url, connection_user, connection_password)
470
- try:
471
- if database is not None and collection is not None:
472
- yield client[database][collection]
473
- elif database is not None:
474
- yield client[database]
475
- else:
476
- yield client
477
- finally:
478
- client.close()
479
-
480
425
 
481
426
  def get_metadata_for_timeseries_collections(db, data_type=None, net_id=None, element_type=None, element_index=None, **kwargs):
482
427
  if element_type is None:
@@ -7,7 +7,8 @@ DATATYPES = {
7
7
  "zone": str,
8
8
  "in_service": bool,
9
9
  "max_vm_pu": float,
10
- "min_vm_pu": float
10
+ "min_vm_pu": float,
11
+ "geo": object
11
12
  },
12
13
  "load": {
13
14
  "name": str,
@@ -95,7 +96,8 @@ DATATYPES = {
95
96
  "type": str,
96
97
  "in_service": bool,
97
98
  "max_loading_percent": float,
98
- "std_type": str
99
+ "std_type": str,
100
+ "geo": object
99
101
  },
100
102
  "trafo": {
101
103
  "name": str,
@@ -306,12 +308,4 @@ DATATYPES = {
306
308
  'initial_run': bool,
307
309
  "recycle": str
308
310
  },
309
- "line_geodata": {
310
- "coords": object
311
- },
312
- "bus_geodata": {
313
- "x": float,
314
- "y": float,
315
- "coords": object
316
- },
317
311
  }
@@ -0,0 +1,78 @@
1
+ from contextlib import contextmanager
2
+ from typing import Iterator
3
+
4
+ from pymongo import MongoClient
5
+ from pymongo.synchronous.collection import Collection
6
+ from pymongo.synchronous.database import Database
7
+
8
+ from pandahub.api.internal.settings import PANDAHUB_GLOBAL_DB_CLIENT, MONGODB_URL, MONGODB_USER, MONGODB_PASSWORD
9
+
10
+ def _get_mongo_client(connection_url: str = MONGODB_URL, connection_user: str = MONGODB_USER,
11
+ connection_password: str = MONGODB_PASSWORD) -> MongoClient:
12
+ mongo_client_args = {
13
+ "host": connection_url,
14
+ "uuidRepresentation": "standard",
15
+ "connect": False,
16
+ }
17
+ if connection_user:
18
+ mongo_client_args |= {
19
+ "username": connection_user,
20
+ "password": connection_password,
21
+ }
22
+ return MongoClient(**mongo_client_args)
23
+
24
+ if PANDAHUB_GLOBAL_DB_CLIENT:
25
+ _global_mongo_client = _get_mongo_client(connection_url=MONGODB_URL, connection_user = MONGODB_USER, connection_password = MONGODB_PASSWORD)
26
+ else:
27
+ _global_mongo_client = None
28
+
29
+
30
+ def get_mongo_client(database: str | None = None, collection: str | None = None, connection_url: str = MONGODB_URL, connection_user: str = MONGODB_USER,
31
+ connection_password: str = MONGODB_PASSWORD) -> Iterator[MongoClient | Database | Collection]:
32
+ if collection is not None and database is None:
33
+ raise ValueError("Must specify database to access a collection!")
34
+ if _global_mongo_client is None:
35
+ client = _get_mongo_client(connection_url, connection_user, connection_password)
36
+ else:
37
+ client = _global_mongo_client
38
+ return _get_db_or_coll(client, database, collection)
39
+
40
+
41
+ @contextmanager
42
+ def mongo_client(database: str | None = None, collection: str | None = None, connection_url: str = MONGODB_URL,
43
+ connection_user: str = MONGODB_USER, connection_password: str = MONGODB_PASSWORD) -> Iterator[MongoClient | Database | Collection]:
44
+ """Contextmanager for pymongo MongoClient / Database / Collection with close after use.
45
+
46
+ Parameters
47
+ ----------
48
+ database: str or None
49
+ The database to connect to
50
+ collection: str or None
51
+ The collection to connect to
52
+ connection_url: str or None
53
+ Defaults to MONGODB_URL env var
54
+ connection_user: str or None
55
+ Defaults to MONGODB_USER env var
56
+ connection_password: str or None
57
+ Defaults to MONGODB_PASSWORD env var
58
+
59
+ Returns
60
+ -------
61
+ Contextmanager yielding MongoClient / Database / Collection
62
+ """
63
+ if collection is not None and database is None:
64
+ raise ValueError("Must specify database to access a collection!")
65
+ client = _get_mongo_client(connection_url, connection_user, connection_password)
66
+ try:
67
+ yield _get_db_or_coll(client, database, collection)
68
+ finally:
69
+ client.close()
70
+
71
+
72
+ def _get_db_or_coll(client: MongoClient, database: str | None = None, collection: str | None = None) -> Iterator[MongoClient | Database | Collection]:
73
+ if database is not None and collection is not None:
74
+ return client[database][collection]
75
+ elif database is not None:
76
+ return client[database]
77
+ else:
78
+ return client
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pandahub
3
- Version: 0.3.16
3
+ Version: 0.4.0
4
4
  Summary: Data hub for pandapower and pandapipes networks based on MongoDB
5
5
  Author-email: Jan Ulffers <jan.ulffers@iee.fraunhofer.de>, Leon Thurner <leon.thurner@retoflow.de>, Jannis Kupka <jannis.kupka@retoflow.de>, Mike Vogt <mike.vogt@iee.fraunhofer.de>, Joschka Thurner <joschka.thurner@retoflow.de>, Alexander Scheidler <alexander.scheidler@iee.fraunhofer.de>
6
6
  License: Copyright (c) 2022 by University of Kassel, Fraunhofer Institute for Energy Economics
@@ -59,7 +59,7 @@ Provides-Extra: test
59
59
  Provides-Extra: all
60
60
  License-File: LICENSE
61
61
 
62
- [![pandapower](https://www.pandapower.org/images/pp.svg)](https://www.pandapower.org) [![pandapipes](https://www.pandapipes.org/images/pp.svg)](https://www.pandapipes.org)
62
+ from lib import get_mongo_client[![pandapower](https://www.pandapower.org/images/pp.svg)](https://www.pandapower.org) [![pandapipes](https://www.pandapipes.org/images/pp.svg)](https://www.pandapipes.org)
63
63
 
64
64
  [![pandahub](https://badge.fury.io/py/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/pypi/pyversions/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/e2nIEE/pandahub/blob/master/LICENSE)
65
65
 
@@ -75,3 +75,45 @@ Swagger UI is available at http://localhost:8002/docs.
75
75
 
76
76
  If you develop on the library and do not need the fastapi app, `docker compose up db -d` starts only the mongodb
77
77
  container.
78
+
79
+ ## MongoClient handling
80
+
81
+ You can supply a mongodb connection string (or the host as connection string and user/password separately) either as arguments to PandaHub
82
+ or by setting the following environment variables:
83
+
84
+ * MONGODB_URL
85
+ * MONGODB_USER (optional)
86
+ * MONGODB_PASSWORD (optional)
87
+
88
+ These values are then used as default arguments when creating a PandaHub object.
89
+
90
+ By default, each instantiation of a PandaHub instance will create a new MongoClient instance. This is fine for interactive use or small scripts. To clean up the mongo client, call `ph.close()` on the instance once you are done with it.
91
+
92
+ When running the pandahub API as a web application, provide database credentials through the environment variables and additionally set `PANDAHUB_GLOBAL_DB_CLIENT` to `true`.
93
+ A single, global mongodb client instance per process will then be used for all PandaHub instantiations, as recommended by [pymongo](https://pymongo.readthedocs.io/en/stable/faq.html#how-does-connection-pooling-work-in-pymongo).
94
+
95
+ You can use the following properties to access database resources from a PandaHub object:
96
+
97
+ * *ph.project_db*: database of the activated project (e.g. `ph.project_db.net_bus.find().to_list()`)
98
+ * *ph.mgmt_db*: the user_management database
99
+ * *ph.users_collection*: the users collection from the management database
100
+ * *ph.projects_collection*: the projects collection from the management database
101
+
102
+
103
+ In case you want to access the database but do not need a PandaHub object, there are two convenience functions available:
104
+
105
+ ```python
106
+ from pandahub.lib import get_mongo_client
107
+
108
+ my_collection = get_mongo_client("db", "collection")
109
+ docs = collection.find().to_list()
110
+ ```
111
+ `PANDAHUB_GLOBAL_DB_CLIENT` is respected, you either get the global client or a fresh instance, in which case you are responsible for cleaning up the connection.
112
+
113
+ ```python
114
+ from pandahub.lib import mongo_client
115
+
116
+ with mongo_client("db", "my_collection") as collection:
117
+ docs = collection.find().to_list()
118
+ ```
119
+ With the context manager, a new MongoClient is created (even if `PANDAHUB_GLOBAL_DB_CLIENT` is `true`) and closed on completion of the with block.
@@ -32,6 +32,7 @@ pandahub/lib/PandaHub.py
32
32
  pandahub/lib/__init__.py
33
33
  pandahub/lib/database_toolbox.py
34
34
  pandahub/lib/datatypes.py
35
+ pandahub/lib/mongo_client.py
35
36
  pandahub/lib/mongodb_indexes.py
36
37
  pandahub/lib/timeseries/__init__.py
37
38
  pandahub/lib/timeseries/output_writer_mongodb.py
@@ -2,9 +2,9 @@ uvicorn>=0.24.0
2
2
  fastapi-users[beanie]>=12.0
3
3
  fastapi>=0.104.0
4
4
  fastapi-mail>=1.4.1
5
- pandapower~=2.14
5
+ pandapower>=3.0
6
6
  pandapipes>=0.7.0
7
- pymongo
7
+ pymongo>=4.9
8
8
  pydantic
9
9
  simplejson
10
10
  requests
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pandahub"
7
- version = "0.3.16" # File format version '__format_version__' is tracked in __init__.py
7
+ version = "0.4.0" # File format version '__format_version__' is tracked in __init__.py
8
8
  authors=[
9
9
  { name = "Jan Ulffers", email = "jan.ulffers@iee.fraunhofer.de" },
10
10
  { name = "Leon Thurner", email = "leon.thurner@retoflow.de" },
@@ -2,9 +2,9 @@ uvicorn>=0.24.0
2
2
  fastapi-users[beanie]>=12.0
3
3
  fastapi>=0.104.0
4
4
  fastapi-mail>=1.4.1
5
- pandapower~=2.14
5
+ pandapower>=3.0
6
6
  pandapipes>=0.7.0
7
- pymongo
7
+ pymongo>=4.9
8
8
  pydantic
9
9
  simplejson
10
10
  requests
pandahub-0.3.16/README.md DELETED
@@ -1,16 +0,0 @@
1
- [![pandapower](https://www.pandapower.org/images/pp.svg)](https://www.pandapower.org) [![pandapipes](https://www.pandapipes.org/images/pp.svg)](https://www.pandapipes.org)
2
-
3
- [![pandahub](https://badge.fury.io/py/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/pypi/pyversions/pandahub.svg)](https://pypi.org/project/pandahub/) [![pandahub](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/e2nIEE/pandahub/blob/master/LICENSE)
4
-
5
- pandahub is a data hub for pandapower and pandapipes networks based on MongoDB. It allows you to store pandapower and
6
- pandapipes networks as well as timeseries in a MongoDB. pandahub allows you to access the database directly through the PandaHub class,
7
- but also provides a REST-API based on FastAPI. Access through the API is managed with a user management implementation based on FastAPI Users.
8
-
9
- ## Development
10
- `docker compose up -d` runs a mongodb container alongside a pandahub api instance with live reload available
11
- at http://localhost:8002. To connect to an existing database instead, set `MONGODB_URL` to the connection string through an environment variable / in you `.env` file.
12
-
13
- Swagger UI is available at http://localhost:8002/docs.
14
-
15
- If you develop on the library and do not need the fastapi app, `docker compose up db -d` starts only the mongodb
16
- container.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes