xmas-app 0.11.2__py3-none-any.whl → 0.12.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.
xmas_app/db.py CHANGED
@@ -73,6 +73,45 @@ def get_db_feature_ids(
73
73
  return results
74
74
 
75
75
 
76
+ def get_db_plans() -> list[dict]:
77
+ """Get plan data from the database.
78
+
79
+ Query the database for plan objects.
80
+
81
+ Returns:
82
+ list[dict]: List of dicts containing plan data, i.e. name, id, appschema, version, last update.
83
+ """
84
+ results = []
85
+ try:
86
+ with settings.repo.Session() as session:
87
+ stmt = select(Feature).where(Feature.featuretype.regexp_match("^.*Plan$"))
88
+ try:
89
+ db_result = session.execute(stmt)
90
+ features = db_result.unique().scalars().all()
91
+ logger.info(f"Found {len(features)} feature(s) in DB.")
92
+ except Exception as db_ex:
93
+ logger.error(f"Database query failed: {db_ex}", exc_info=True)
94
+ for feature in features:
95
+ plan_data = {
96
+ "id": str(feature.id),
97
+ "label": feature.properties.get("name", "<Name unbekannt>"),
98
+ "appschema": {
99
+ "name": feature.appschema.upper(),
100
+ "version": feature.version,
101
+ },
102
+ "updated": {
103
+ "date": f"{feature.updated_at.strftime('%m.%d.%Y')}",
104
+ "time": f"{feature.updated_at.strftime('%H:%M:%S Uhr')}",
105
+ },
106
+ }
107
+ results.append(plan_data)
108
+
109
+ except Exception as ex:
110
+ logger.error(f"Failed to get plan data: {ex}", exc_info=True)
111
+ raise
112
+ return results
113
+
114
+
76
115
  def delete_db_association(base_id: str, related_id: str, repo: DBRepository):
77
116
  try:
78
117
  with repo.Session() as session:
xmas_app/form.py CHANGED
@@ -627,7 +627,7 @@ class ModelForm:
627
627
  class_info.move(self.header if not preview else self.parent, 0)
628
628
  with ui.grid(columns="310px auto"):
629
629
  for key, field_info in model.model_fields.items():
630
- if key == "id" or key == model.get_geom_field():
630
+ if key == model.get_geom_field():
631
631
  continue
632
632
  prop_info = model.get_property_info(key)
633
633
  if preview and (
@@ -661,7 +661,11 @@ class ModelForm:
661
661
  with ui.expansion(key).props(
662
662
  "expand-icon=o_info dense dense-toggle header-class=text-bold"
663
663
  ):
664
- ui.label(field_info.description or "")
664
+ ui.label(
665
+ "Identifikator des Features."
666
+ if key == "id"
667
+ else field_info.description or ""
668
+ )
665
669
  with ui.row() as property_row:
666
670
  if main_form and not preview:
667
671
  property_row.bind_visibility_from(
@@ -679,6 +683,16 @@ class ModelForm:
679
683
  if prop_info["nullable"]:
680
684
  marker.append("nullable")
681
685
  property_row.mark(" ".join(marker))
686
+ if key == "id":
687
+ input = (
688
+ ui.label()
689
+ .bind_text_from(
690
+ bind_obj,
691
+ key,
692
+ )
693
+ .classes("w-full")
694
+ )
695
+ continue
682
696
  if key == "stylesheetId":
683
697
  input = (
684
698
  ui.label()
@@ -691,7 +705,6 @@ class ModelForm:
691
705
  )
692
706
  .classes("w-full")
693
707
  )
694
- # input.disable()
695
708
  continue
696
709
  match prop_info["stereotype"]:
697
710
  case "BasicType":
xmas_app/main.py CHANGED
@@ -29,7 +29,7 @@ from xplan_tools.util import (
29
29
  get_geometry_type_from_wkt,
30
30
  )
31
31
 
32
- from xmas_app.db import get_db_feature_ids, get_nodes
32
+ from xmas_app.db import get_db_feature_ids, get_db_plans, get_nodes
33
33
  from xmas_app.deps.version_guard import enforce_plugin_version
34
34
  from xmas_app.form import ModelForm
35
35
  from xmas_app.models.crud import InsertPayload, UpdatePayload
@@ -257,23 +257,23 @@ async def plan_tree(
257
257
  on_click=lambda e, target=target: confirm_delete(target),
258
258
  )
259
259
  elif len(path) == 3:
260
- origin_featuretype, origin_id, attr = path
260
+ origin_featuretype, origin_id, rel = path
261
261
  with action_dialog, ui.card(align_items="stretch"):
262
262
  ui.label(origin_featuretype + " " + origin_id[:8]).classes(
263
263
  "text-bold"
264
264
  )
265
- if attr == "bereich":
266
- ui.label(f"Keine Aktionen für Referenz {repr(attr)} verfügbar")
265
+ if rel == "bereich":
266
+ ui.label(f"Keine Aktionen für Referenz {repr(rel)} verfügbar")
267
267
  else:
268
- ui.label(f"Neues Feature für Referenz {repr(attr)} erstellen")
268
+ ui.label(f"Neues Feature für Referenz {repr(rel)} erstellen")
269
269
  feature = settings.repo.get(origin_id)
270
- prop_info = feature.get_property_info(attr)
270
+ prop_info = feature.get_property_info(rel)
271
271
  typename = prop_info["typename"]
272
272
  targets = typename if isinstance(typename, list) else [typename]
273
273
  target_select = ui.select(
274
274
  targets,
275
275
  label="Featuretype auswählen",
276
- value=targets[0],
276
+ value=None,
277
277
  with_input=True,
278
278
  )
279
279
  if len(targets) == 1:
@@ -281,17 +281,103 @@ async def plan_tree(
281
281
  with target_select.add_slot("append"):
282
282
  ui.badge(str(len(targets))).props("floating")
283
283
  with target_select.add_slot("after"):
284
- ui.button(
284
+ dropdown = ui.dropdown_button(
285
285
  icon="play_circle",
286
- on_click=lambda _: ui.notify(
287
- "noch nicht implementiert"
288
- ),
286
+ split=True,
289
287
  )
288
+ target_select.on_value_change(
289
+ lambda e,
290
+ origin_featuretype=origin_featuretype,
291
+ origin_id=origin_id,
292
+ rel=rel: prepare_add_feature(
293
+ dropdown,
294
+ origin_featuretype,
295
+ origin_id,
296
+ rel,
297
+ e.value,
298
+ )
299
+ )
300
+ if len(targets) == 1:
301
+ target_select.set_value(targets[0])
302
+ else:
303
+ dropdown.props("disable")
290
304
 
291
305
  else:
292
306
  return
293
307
  action_dialog.open()
294
308
 
309
+ async def prepare_add_feature(
310
+ dropdown: ui.dropdown_button,
311
+ origin_featuretype: str,
312
+ origin_id: str,
313
+ rel: str,
314
+ target_featuretype: str,
315
+ ):
316
+ def trigger_add_feature(geometry_type):
317
+ rel_inv = origin_feature.get_property_info(rel)["assoc_info"]["reverse"]
318
+ data = {
319
+ "origin_featuretype": origin_featuretype,
320
+ "origin_id": origin_id,
321
+ "rel": rel,
322
+ "rel_list": origin_feature.get_property_info(rel)["list"],
323
+ "rel_inv": rel_inv,
324
+ "rel_inv_list": target_model.get_property_info(rel_inv)["list"]
325
+ if rel_inv
326
+ else False,
327
+ "target_featuretype": target_featuretype,
328
+ "target_geometrytype": geometry_type,
329
+ }
330
+ print(data)
331
+ try:
332
+ logger.info("Sending add_feature to QWebChannel handler")
333
+ ui.run_javascript(f"""new QWebChannel(qt.webChannelTransport, function (channel) {{
334
+ channel.objects.handler.add_feature({data})
335
+ }});""")
336
+ except Exception as ex:
337
+ logger.error(f"Exception while add_feature called: {ex}", exc_info=True)
338
+
339
+ GEOMETRY_MAP = {
340
+ "Polygon": {"label": "Fläche", "icon": "o_space_dashboard"},
341
+ "Line": {"label": "Linie", "icon": "show_chart"},
342
+ "Point": {"label": "Punkt", "icon": "o_location_on"},
343
+ "Null": {"label": "Keine Geometrie", "icon": "o_text_snippet"},
344
+ }
345
+
346
+ logger.debug("add_feature called")
347
+ dropdown.clear()
348
+ dropdown.props(remove="disable")
349
+ origin_feature = settings.repo.get(origin_id)
350
+ target_model = model_factory(
351
+ target_featuretype,
352
+ origin_feature.get_version(),
353
+ origin_feature.get_appschema(),
354
+ )
355
+ geometry_types = []
356
+ if geom_types := target_model.get_geom_types():
357
+ for geom_type in geom_types:
358
+ type_name = geom_type.get_name().replace("Multi", "")
359
+ if type_name not in geometry_types:
360
+ geometry_types.append(type_name)
361
+ else:
362
+ geometry_types.append("Null")
363
+ if len(geometry_types) == 1:
364
+ dropdown.on_click(lambda: trigger_add_feature(geometry_types[0]))
365
+ dropdown.props("disable-dropdown")
366
+ else:
367
+ with dropdown:
368
+ dropdown.on_click(lambda e: e.sender.open())
369
+ dropdown.props(remove="disable-dropdown")
370
+ for geometry_type in geometry_types:
371
+ with ui.item(
372
+ on_click=lambda geometry_type=geometry_type: trigger_add_feature(
373
+ geometry_type
374
+ ),
375
+ ):
376
+ with ui.item_section().props("avatar"):
377
+ ui.icon(GEOMETRY_MAP[geometry_type]["icon"])
378
+ with ui.item_section():
379
+ ui.item_label(GEOMETRY_MAP[geometry_type]["label"])
380
+
295
381
  async def highlight_feature(e: ClickEventArguments, target: str):
296
382
  logger.debug("highlight_feature called")
297
383
  try:
@@ -426,35 +512,31 @@ async def plans(
426
512
  logger.info("Updating plan select options")
427
513
  plan_select.props("loading")
428
514
  try:
429
- feature_ids = await asyncio.wait_for(
430
- run.io_bound(
431
- get_db_feature_ids,
432
- settings.repo,
433
- None,
434
- "^.*Plan$",
435
- "name",
436
- ),
515
+ selected = plan_select.options.index(plan_select.value)
516
+ except ValueError:
517
+ selected = 0
518
+ try:
519
+ plans = await asyncio.wait_for(
520
+ run.io_bound(get_db_plans),
437
521
  timeout=10,
438
522
  )
439
- if not feature_ids: # Handle empty result or None
523
+ if not plans: # Handle empty result or None
440
524
  logger.warning("No plans returned from the database.")
441
525
  ui.notify(
442
526
  "Keine Pläne gefunden (möglicherweise keine Verbindung zur Datenbank)!",
443
527
  type="negative",
444
528
  )
445
- plan_select.set_options({})
529
+ plan_select.set_options([])
446
530
  else:
447
- if feature_ids is not None and feature_ids:
448
- plan_select.set_options(feature_ids)
449
- plan_select.set_value(list(feature_ids.keys())[0])
450
- else:
451
- logger.warning("No plans found or database unreachable.")
531
+ plan_select.set_options(plans)
532
+ plan_select.set_value(plans[selected])
533
+
452
534
  except asyncio.TimeoutError:
453
535
  logger.error("Timeout: DB connection took too long.")
454
536
  ui.notify(
455
537
  "Verbindung zur Datenbank dauert zu lange (Timeout)!", type="negative"
456
538
  )
457
- plan_select.set_options({})
539
+ plan_select.set_options([])
458
540
  except Exception as e:
459
541
  print("Fehler beim Laden der Pläne:", e)
460
542
  logger.error(f"Fehler beim Laden der Pläne: {e}")
@@ -465,7 +547,7 @@ async def plans(
465
547
  ui.notify("Keine Verbindung zur Datenbank!", type="negative")
466
548
  else:
467
549
  ui.notify(f"Fehler beim Laden der Pläne: {e}", type="negative")
468
- plan_select.set_options({})
550
+ plan_select.set_options([])
469
551
  finally:
470
552
  plan_select.props(remove="loading")
471
553
 
@@ -476,10 +558,12 @@ async def plans(
476
558
  )
477
559
  try:
478
560
  download_dropdown.props(add="loading")
479
- plan = await run.io_bound(settings.repo.get_plan_by_id, plan_select.value)
561
+ plan = await run.io_bound(
562
+ settings.repo.get_plan_by_id, plan_select.value["id"]
563
+ )
480
564
 
481
- appschema = plan.features[plan_select.value].get_data_type()
482
- version = plan.features[plan_select.value].get_version()
565
+ appschema = plan.features[plan_select.value["id"]].get_appschema()
566
+ version = plan.features[plan_select.value["id"]].get_version()
483
567
  logger.debug(
484
568
  "Loaded plan metadata: version=%s schema=%s", version, appschema
485
569
  )
@@ -490,17 +574,14 @@ async def plans(
490
574
  ) # TODO get data from in-memory sqlite db?
491
575
  logger.debug("Using temporary sqlite file %s", temp_file.name)
492
576
  uri = f"gpkg:///{temp_file.name}"
493
- repo = DBRepository(uri, version, appschema)
494
- repo.create_tables(srid=plan.srid)
577
+ repo = DBRepository(uri, srid=plan.srid)
495
578
  repo.save_all(plan)
496
579
  data = Path(temp_file.name).read_bytes()
497
580
  Path(temp_file.name).unlink()
498
581
  else:
499
582
  buffer = io.BytesIO()
500
583
  logger.debug("Serializing to %s via repo_factory", format)
501
- repo_factory(buffer, version, appschema, repo_type=format).save_all(
502
- plan
503
- )
584
+ repo_factory(buffer, repo_type=format).save_all(plan)
504
585
  data = buffer.getvalue()
505
586
 
506
587
  logger.info("Prepared download payload (%d bytes)", len(data))
@@ -533,22 +614,23 @@ async def plans(
533
614
 
534
615
  e.sender.props(add="loading")
535
616
  try:
536
- await run.io_bound(settings.repo.delete_plan_by_id, plan_select.value)
617
+ await run.io_bound(settings.repo.delete_plan_by_id, plan_select.value["id"])
537
618
  logging.info("Plan %s deleted successfully", plan_select.value)
538
619
 
539
620
  options = plan_select.options
540
- options.pop(plan_select.value)
621
+ options.remove(plan_select.value)
541
622
  plan_select.set_options(options)
542
623
 
543
624
  except Exception:
544
625
  logging.exception("Failed to delete plan %s", plan_select.value)
545
- ui.notification.toast("Fehler beim Löschen des Plans.", severity="error")
626
+ ui.notify("Fehler beim Löschen des Plans.", type="negative")
546
627
 
547
628
  finally:
548
629
  e.sender.props(remove="loading")
549
630
 
550
631
  async def handle_load_plan(e: ClickEventArguments):
551
- """
632
+ """Load a plan in QGIS.
633
+
552
634
  Handles loading a selected plan and sends the plan data to the QWebChannel handler.
553
635
  Fetches the selected plan, retrieves its associated 'bereiche', and passes the structured
554
636
  data to the web view. Logs key steps and errors, and provides user feedback on failure.
@@ -560,7 +642,7 @@ async def plans(
560
642
  e.sender.props(add="loading")
561
643
  try:
562
644
  logger.info(f"Attempting to load plan with ID: {plan_select.value}")
563
- plan = await run.io_bound(settings.repo.get, plan_select.value)
645
+ plan = await run.io_bound(settings.repo.get, plan_select.value["id"])
564
646
  if plan is None:
565
647
  logger.error(f"No plan found for ID: {plan_select.value}")
566
648
  ui.notify("Fehler: Plan nicht gefunden", type="negative")
@@ -593,7 +675,7 @@ async def plans(
593
675
  plan_data = {
594
676
  "plan_id": plan.id,
595
677
  "plan_name": plan.name,
596
- "appschema": plan.get_data_type(),
678
+ "appschema": plan.get_appschema(),
597
679
  "version": plan.get_version(),
598
680
  "plan_type": plan.get_name(),
599
681
  "bereiche": bereiche,
@@ -617,7 +699,7 @@ async def plans(
617
699
  async def handle_import_plan(e):
618
700
  data = io.BytesIO(e.content.read())
619
701
  try:
620
- repo = GMLRepository(data, data_type=appschema)
702
+ repo = GMLRepository(data)
621
703
  except Exception as e:
622
704
  print(e)
623
705
  return ui.notify("Fehler beim Lesen der Datei", type="negative")
@@ -643,10 +725,29 @@ async def plans(
643
725
  with ui.column(align_items="start").classes("w-full"):
644
726
  ui.label("Vorhandene Pläne").classes("text-h6")
645
727
  plan_select = (
646
- ui.select({}, label="Plan auswählen", with_input=True)
647
- .props("loading")
648
- .classes("w-full")
728
+ ui.select([], label="Plan auswählen", with_input=True)
729
+ .props("loading :option-label='(opt) => opt.label.label'")
730
+ .classes("w-full dense")
731
+ )
732
+ plan_select.add_slot(
733
+ "option",
734
+ r"""
735
+ <q-item v-bind="props.itemProps">
736
+ <q-item-section>
737
+ <q-item-label>{{ props.opt.label.label }}</q-item-label>
738
+ <q-item-label caption>{{ props.opt.label.updated.date }} {{ props.opt.label.updated.time }}</q-item-label>
739
+ </q-item-section>
740
+ <q-item-section side top>
741
+ <q-item-label caption>{{ props.opt.label.appschema.name }}</q-item-label>
742
+ <q-item-label caption>v{{ props.opt.label.appschema.version }}</q-item-label>
743
+ </q-item-section>
744
+ </q-item>
745
+ """,
649
746
  )
747
+ with plan_select.add_slot("append"):
748
+ ui.badge().bind_text_from(
749
+ plan_select, "options", backward=lambda x: len(x)
750
+ ).props("floating")
650
751
  with plan_select.add_slot("after"):
651
752
  ui.button(icon="refresh", on_click=update_plan_select_options)
652
753
  ui.button(
xmas_app/models/crud.py CHANGED
@@ -17,7 +17,7 @@ class InsertPayload(RootModel):
17
17
 
18
18
  class UpdateItem(BaseModel):
19
19
  properties: dict = {}
20
- geometry: dict | None
20
+ geometry: dict | None = None
21
21
 
22
22
 
23
23
  class UpdatePayload(RootModel):
xmas_app/settings.py CHANGED
@@ -14,7 +14,7 @@ class AppSchema(TypedDict):
14
14
 
15
15
 
16
16
  APPSCHEMAS: Dict[str, AppSchema] = {
17
- "XPlanung": {"type": "xplan", "versions": ["5.4", "6.0"]},
17
+ "XPlanung": {"type": "xplan", "versions": ["5.4", "6.0", "6.1"]},
18
18
  "XTrasse": {"type": "xtrasse", "versions": ["2.0"]},
19
19
  }
20
20
 
@@ -55,7 +55,7 @@ class Settings(BaseSettings):
55
55
  "https://registry.gdi-de.org/codelist/de.xleitstelle.xplanung"
56
56
  )
57
57
  qgis_plugin_name: str = "XMAS-Plugin"
58
- qgis_plugin_min_version: _VersionPydanticAnnotation = Version(major=0, minor=10)
58
+ qgis_plugin_min_version: _VersionPydanticAnnotation = Version(major=0, minor=13)
59
59
 
60
60
  model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
61
61
 
@@ -76,8 +76,6 @@ class Settings(BaseSettings):
76
76
  )
77
77
  self.repo = DBRepository(
78
78
  "postgresql://",
79
- self.appschema_version,
80
- self.appschema,
81
79
  srid=self.srid,
82
80
  with_views=True,
83
81
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xmas-app
3
- Version: 0.11.2
3
+ Version: 0.12.0
4
4
  Summary: The XLeitstelle model-driven application schema app.
5
5
  License: EUPL-1.2-or-later
6
6
  License-File: LICENSE
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3
15
15
  Requires-Dist: nicegui (>=2.20,<3.0)
16
16
  Requires-Dist: pydantic-extra-types[semver]
17
17
  Requires-Dist: pydantic-settings (>=2.0,<3.0)
18
- Requires-Dist: xplan-tools (>=1.10.3)
18
+ Requires-Dist: xplan-tools (>=1.11.0,<1.12.0)
19
19
  Project-URL: Homepage, https://gitlab.opencode.de/xleitstelle/xmas-app
20
20
  Project-URL: Issues, https://gitlab.opencode.de/xleitstelle/xmas-app/-/issues
21
21
  Description-Content-Type: text/markdown
@@ -119,7 +119,7 @@ To import all .gml files at once, extract the archive and use:
119
119
  (on Linux/Mac/Wsl Bash):
120
120
  ```bash
121
121
  for f in <pfad>/bp/*.gml; do
122
- xplan-tools convert "$f" postgresql://postgres:postgres@localhost:5432/postgres
122
+ xplan-tools convert "$f" postgresql://postgres:postgres@localhost:55432/postgres
123
123
  done
124
124
  ```
125
125
 
@@ -1,20 +1,20 @@
1
- xmas_app/db.py,sha256=dHU8_xDsH2WduEMrYs0spY55hLlN5Xv12tRb8lHAado,7139
1
+ xmas_app/db.py,sha256=GRlThmWxRL1xKrxeJ2q3cUxBDKZBosUORAwkyUWAR4g,8632
2
2
  xmas_app/db_uow.py,sha256=usvqo1bO4dQnMbiub5PyQAAgu8Af-HYAJTsFNYS3Le0,761
3
3
  xmas_app/deps/version_guard.py,sha256=iBVLOKfffdiMXI6dnU62TmVIUqOYV6BQP8KoF814Gu4,840
4
- xmas_app/form.py,sha256=fO-7Fwl3TCEQIlZYrbathPIZ1iBIk7nkVLnLaN5vDbM,70954
5
- xmas_app/main.py,sha256=wvEo4ntXmqmpRLyUCoOQr13jPL2nNzTJbtWM8KeOLzI,50513
6
- xmas_app/models/crud.py,sha256=p_NkUXAo9UkUPUPh2yPtuWoUnP2yMONjxgPaMmgkhzw,411
4
+ xmas_app/form.py,sha256=2HaYmTkvj98lR77WEeg8hRYkTyAEukfz-1AfHt0-9y8,71426
5
+ xmas_app/main.py,sha256=MV09N1C_DoSoOunHOfFYJYCi9XensS-lLNbC2SG734Y,55101
6
+ xmas_app/models/crud.py,sha256=Rtz-v2FpMM4AcTc_wcye_XjZtssTydgtSQcKuIK1yiM,418
7
7
  xmas_app/pygeoapi/config.yaml,sha256=lmb08zXk8-dB0sh_4EQ1enVBIqgBI-v6QcoIiHyH3N8,7992
8
8
  xmas_app/pygeoapi/openapi.yaml,sha256=h4J4kYugyNVMFjL_C2CLcntELgIkGh0N4VzpPf8u_tE,40650
9
9
  xmas_app/pygeoapi/provider.py,sha256=uKHK_Hsqale1C2pNRKYpyka9GnNHYvGBmbzfcSi05NE,25109
10
10
  xmas_app/schema.py,sha256=3P-i14caMqydGsxNw6SOAcanEg1Qr4qxqciAow1aCUM,1507
11
11
  xmas_app/services/crud.py,sha256=qY6d_bXKSNN-6lID8h7vVeYAvwOb8A6JkhZpaWLRIE8,2464
12
- xmas_app/settings.py,sha256=ojXnVZk3kSCuueN84CeDX7Ts95rptZ8dfzn_eB9HDCE,2538
12
+ xmas_app/settings.py,sha256=4MUQKRJzoAdCUhgAc3BqenkrBcvbynNmVFbsJ2iugpo,2481
13
13
  xmas_app/split_service.py,sha256=Ag_Ix2vMgT04OsQ0DIGK7WFKQlbktmmTqWlESO2ZgVs,26701
14
14
  xmas_app/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  xmas_app/util/codelist.py,sha256=7qdiaKQLm38TA7WuAfizmdXd_AjJVZ-tnAFpQg4vdeA,1951
16
- xmas_app-0.11.2.dist-info/METADATA,sha256=lKzGZ6mu9VfyOWS7eT9iSZVidP9p1x8KIwvOGVSNX6k,5315
17
- xmas_app-0.11.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
18
- xmas_app-0.11.2.dist-info/entry_points.txt,sha256=uve2b0hgPbVfiD047-hOI6_dD97ZytvCPWk7HsN9Xng,53
19
- xmas_app-0.11.2.dist-info/licenses/LICENSE,sha256=b8nnCcy_4Nd_v_okJ6mDKCvi64jkexzbSfIag7TR5mU,13827
20
- xmas_app-0.11.2.dist-info/RECORD,,
16
+ xmas_app-0.12.0.dist-info/METADATA,sha256=UPohWq_NV7RTIyWNSNtmDaURORgQIPZZk8KqUvpLflo,5324
17
+ xmas_app-0.12.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
18
+ xmas_app-0.12.0.dist-info/entry_points.txt,sha256=uve2b0hgPbVfiD047-hOI6_dD97ZytvCPWk7HsN9Xng,53
19
+ xmas_app-0.12.0.dist-info/licenses/LICENSE,sha256=b8nnCcy_4Nd_v_okJ6mDKCvi64jkexzbSfIag7TR5mU,13827
20
+ xmas_app-0.12.0.dist-info/RECORD,,